一.IntentService的介绍
很多时候我们创建Service就是希望Service能在后台进行一些耗时操作,但是又由于Service默认是运行在主线程的,我们不太方便直接进行耗时操作,然后每次进行耗时操作我们都得手动创建新的线程,显得很麻烦。
所以就会有IntentService,它内部是帮我们创建了一个HandleThread,以及对应的Handler,并通过onStart方法把intent封装成message再send给了Handler,Handler就在他的handleMessage中会将Intent从Message中取出来,然后直接调用我们重写的onHandlerIntent去处理这个intent。
我们只需要在 onHandleIntent()
方法中去实现我们的业务代码即可。调用就是通过简单的startService去调用。
- 它本质是一种特殊的Service,继承自Service并且本身就是一个抽象类
- 它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止
- 它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务
- 它内部通过HandlerThread和Handler实现异步操作
- 创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作
二.IntentService的使用姿势
- 通过借助Intent调用startService方法启动 IntentServcie并传递传递数据到IntentService
- startService(context, intentServcie.class)方法调用后,会把任务通过Intent传递到onHandleIntent() ,这个方法就是在子线程中运行的,进行一些网络请求等的耗时操作。
- 最后通过接口+Handler转换到主线程更新UI(也可通过Broadcast等方法传递消息到Activity)
看下面的demo:
通过IntentService从网上获取图片并显示
public class MyIntentService extends IntentService {
private static final String ACTION_FOO = "com.example.a11_text1.action.FOO";
private static final String EXTRA_PARAM1 = "com.example.a11_text1.extra.PARAM1";
private static final String EXTRA_PARAM2 = "com.example.a11_text1.extra.PARAM2";
//接口,用于更新ui时传递数据到Activity
public static UpdateUI updateUI;
private static int index;
public MyIntentService() {
super("MyIntentService");
}
public static void setUpdateUI(UpdateUI updateUIInterface){
updateUI=updateUIInterface;
}
public static void startActionFoo(Context context, String param1,int i) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_FOO);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2,i);
index = i;
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_FOO.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
handleActionFoo(param1);
}
}
}
private void handleActionFoo(String param1) {
Bitmap bitmap = downloadUrlBitmap(param1);
Message msg = new Message();
ImageModel imageModel = new ImageModel();
imageModel.setBitmap(bitmap);
msg.obj = imageModel;
msg.what = index;
if(updateUI!=null){
updateUI.upData(msg);
}
}
private Bitmap downloadUrlBitmap(String param1) {
FutureTarget<Bitmap> futureTarget =
Glide.with(getApplicationContext())
.asBitmap()
.load(param1).submit(100,100);
Bitmap bitmap = null;
try {
bitmap = futureTarget.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return bitmap;
}
}
public class MainActivity extends AppCompatActivity {
Handler mainHandler,workHandler;
HandlerThread mHandlerThread;
private final String[] url ={
"https://img-blog.csdn.net/20160903083245762",
"https://img-blog.csdn.net/20160903083252184",
"https://img-blog.csdn.net/20160903083257871",
"https://img-blog.csdn.net/20160903083257871",
"https://img-blog.csdn.net/20160903083311972",
"https://img-blog.csdn.net/20160903083319668",
"https://img-blog.csdn.net/20160903083326871"
};
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread);
initView();
initData();
}
private void initView() {
imageView= (ImageView) findViewById(R.id.image);
mainHandler = new Handler(getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
ImageModel model = (ImageModel) msg.obj;
if(model.getBitmap()!=null){
imageView.setImageBitmap(model.getBitmap());
}
}
};
}
private void initData() {
IS_DownloadThePictures();
}
private void IS_DownloadThePictures() {
UpdateUI updateUI = message -> mainHandler.sendMessageDelayed(message, 1000);
MyIntentService.setUpdateUI(updateUI);
for(int i = 0; i < 7; i++){
startActionFoo(MainActivity.this,url[i],i);
}
}
}
注意即使我们多次启动IntentService,但IntentService的实例只有一个,这跟传统的Service是一样的,最终IntentService会去调用onHandleIntent执行异步任务。这里可能我们还会担心for循环去启动任务,而实例又只有一个,那么任务会不会被覆盖掉呢?其实是不会的,因为IntentService真正执行异步任务的是HandlerThread+Handler,每次启动都会把下载图片的任务添加到依附的消息队列中,最后由HandlerThread+Handler去执行。
三.源码分析
我们先来看看IntentService的onCreate方法:
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
当第一次启动IntentService时,它的onCreate方法将会被调用,其内部会去创建一个HandlerThread并启动它,接着创建一个ServiceHandler(继承Handler),传入HandlerThread的Looper对象,这样ServiceHandler就变成可以处理异步线程的执行类了。那么IntentService是怎么启动异步任务的呢?其实IntentService启动后还会去调用onStartCommand方法,而onStartCommand方法又会去调用onStart方法,我们看看它们的源码:
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
在onStart方法中,IntentService通过mServiceHandler的sendMessage方法发送了一个消息,这个消息将会发送到HandlerThread中进行处理(因为HandlerThread持有Looper对象,所以其实是Looper从消息队列中取出消息进行处理,然后调用mServiceHandler的handleMessage方法),我们看看ServiceHandler的源码:
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
在这里面调用了onHandleIntent方法,执行我们要执行的异步操作,执行结束后,IntentService会通过 stopSelf(int startId)方法来尝试停止服务。这里采用stopSelf(int startId)而不是stopSelf()来停止服务 ,是因为stopSelf()会立即停止服务,而stopSelf(int startId)会等待所有消息都处理完后才终止服务
这里要注意的是如果后台任务只有一个的话,onHandleIntent执行完,服务就会销毁,但如果后台任务有多个的话,onHandleIntent执行完最后一个任务时,服务才销毁。最后我们要知道每次执行一个后台任务就必须启动一次IntentService,而IntentService内部则是通过消息的方式发送给HandlerThread的,然后由Handler中的Looper来处理消息,而Looper是按顺序从消息队列中取任务的,也就是说IntentService的后台任务时顺序执行的,当有多个后台任务同时存在时,这些后台任务会按外部调用的顺序排队执行,
四.面试题归纳
- 为何不用bindService方式创建IntentService?
IntentService的工作原理是,在IntentService的onCreate()里会创建一个HandlerThread,并利用其内部的Looper实例化一个ServiceHandler对象;而这个ServiceHandler用于处理消息的handleMessage()方法会去调用IntentService的onHandleIntent(),这也是为什么可在该方法中处理后台任务的逻辑;当有Intent任务请求时会把Intent封装到Message,然后ServiceHandler会把消息发送出,而发送消息是在onStartCommand()完成的,只能通过startService()才可走该生命周期方法,因此不能通过bindService创建IntentService。