好久没复习IntentService了,直到昨天面试问到了IntentService相关知识点,这里就重新总结了一下,希望对大家掌握IntentService有一臂之力。
从名字上看,很直观地可以看出它是Service的子类,Service对于我们Android开发来讲再熟悉不过了。Service是Android四大组件之一,对用户不可见,但它也是运行在主线程的,那IntentService的特点也跟Service也是一样吗?我们带着这个问题往下分析。
我们知道做Android开发,为啥会有Android初级工程师、Android中级工程师、Android高级工程师以及Android资深工程师甚至是Android专家了?这些分明别类,无非就是对Android整体知识理论及原理的掌握程序和在实际开发运用程度。
所以我从入门级开始给大家讲解一下IntentService的使用,注意了,作为开发首先你得先会用它吧,说得不好听点,你用都不会用,怎么可能知道它的原理呢?
一、IntentService的简单使用
首先,我们知道IntentService是继承自Service,而且它是abstract,这个意味着什么?很简单,我们要重写它。接下来我们通过一个简单的例子来说明IntentService的使用,超级简单,Android入门级的代码书写~
新建一个工程IntentServiceTest,创建完之后,我们新建一个类叫做MyIntentService,让它继承自IntentService,具体代码如下:
package com.example.intentservicetest;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.Nullable;
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
super.onStart(intent, startId);
Log.i("Andrew", "onStart------------");
}
@Override
public void onCreate() {
super.onCreate();
Log.i("Andrew", "onCreate------------");
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.i("Andrew", "onStartCommand------------");
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String taskName = intent.getExtras().getString("taskName");
switch (taskName) {
case "task1":
Log.i("Andrew", "do task1");
break;
case "task2":
Log.i("Andrew", "do task2");
break;
default:
break;
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("Andrew", "onDestroy------------");
}
}
从上面代码可以看出,我们主要重写了IntentService的onStart、onStartCommond、onCreate、onHandleIntent以及onDestroy方法,我们重点关注下onHandleIntent方法,这是核心方法,这里是做一些耗时任务的处理,当收到发送端intent后,它会取出intent相关信息,然后作相应的处理。
接下来我们应当在AndroidManifest.xml中注册MyIntentService,因为我们知道Android的四大组件创建后,都必须要在清单文件中去创建它,别忘记啦,这是常识。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.intentservicetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.IntentServiceTest">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyIntentService">
<intent-filter>
<action android:name="com.het.service" />
</intent-filter>
</service>
</application>
</manifest>
MyIntentService创建并注册完成之后,我们就可以使用它了,我们在MainActivity.java中简单调用一下:
package com.example.intentservicetest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent("com.het.service");
Bundle bundle = new Bundle();
bundle.putString("taskName", "task1");
intent.putExtras(bundle);
startService(intent);
}
}
是不是比预想的要简单的多?别急,最终我们还是要调试运行下结果,因为结果才是最有力的证明。我们在Logcat控制台,抓取下相关日志,Oh~~No,报错了,我们错误log贴出来,具体如下:
这个报错的原因是5.0以上service不能使用隐式intent启动,那我们对intent设置一下包名即可:
Intent intentTwo = new Intent("com.het.service");
Bundle bundleTwo = new Bundle();
bundleTwo.putString("taskName", "task2");
intentTwo.putExtras(bundleTwo);
intentTwo.setPackage("com.example.intentservicetest");
startService(intentTwo);
修改完之后我们再调试运行,结果如下:
com.example.intentservicetest I/Andrew: onCreate------------
com.example.intentservicetest I/Andrew: onStartCommand------------
com.example.intentservicetest I/Andrew: onStart------------
com.example.intentservicetest I/Andrew: do task1
com.example.intentservicetest I/Andrew: onDestroy------------
从打印日志和MyIntentService的代码,我们可以看出每个方法的执行顺序,最重要的一点就是MyIntentService执行完后,自动stop了,这相对service的stopService还是不一样的,这点我们要特别记住,Android面试中很容易考到。
二、面试常考点
1、IntentService为何能执行耗时任务?它的实现原理是什么?
2、IntentService为何执行完任务会自动destroy?
三、IntentService源码解析
我们带着上面两个问题来一起看下IntentService的源码,通过源码我们就能很清楚的知道答案(针对源码我在互联网上找了相关资料,源码带有中文解析):
/**
* IntentService是一种特殊的Service,它继承于Service并且还是个抽象类
* 所以我们必须创建它的子类才能使用IntentService
* IntentService可以用来执行后台任务,当任务执行完后就会“自杀”
* 因为它自己也是个服务,所以优先级高于普通的线程,不容易被系统所干掉
* 所以IntentService比较适合执行优先级较高的后台任务
*/
public abstract class IntentService extends Service {
//HandlerThread的looper
private volatile Looper mServiceLooper;
//通过looper创建的一个Handler对象,用于处理消息
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
/**
* 通过HandlerThread线程中的looper对象构造的一个Handler对象
* 可以看到这个handler中主要就是处理消息
* 并且将我们的onHandlerIntent方法回调出去
* 然后停止任务,销毁自己
*/
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
/**
* 收到消息后,会将Intent对象传递给onHandlerIntent方法去处理
* 注意这个Intent对象的内容和外界Activity中startService(intent)
* 中的intent的内容是完全一致的
* 通过这个intent对象可以得到外界启动IntentService时所传递的参数
* 通过这些参数我们就可以区分不同的业务逻辑
* 这样onHandlerIntent就可以对不同的逻辑做出不同的操作了
* 当onHandlerIntent方法执行结束后,IntentService会通过
* stopSelf(int startId)方法尝试停止服务
* 之所以使用stopSelf(int startId)而不是stopSelf()来停止
* 是因为stopSelf()会马上停止服务,但是有可能还有消息未处理
* stopSelf(int startId)则会等所有的消息都处理完后才销毁自己
* 一般来说的话,stopSelf(int startId)在停止之前会判断
* 最近启动服务的次数和startId是不是相等的,如果相等就立刻停止
* 如果不相等说明还有别的消息没处理,就不停止服务
* 具体的要看AMS中的stopServiceToken方法的实现
*/
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
/**
* 构造函数,可以传递一个name参数
*/
public IntentService(String name) {
super();
mName = name;
}
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
/**
* 当我们的IntentService第一次启动的时候,onCreate方法会执行一次
* 可以看到方法里创建了一个HandlerThread
* HandlerThread继承Thread,它是一种可以使用Handler的Thread
* 它的run方法里通过Looper.prepare()来创建消息队列
* 并通过Looper.loop()来开启消息循环,所以就可以在其中创建Handler了
* 这里我们通过HandlerThread得到一个looper对象
* 并且使用它的looper对象来构造一个Handler对象,就是我们上面看到的那个
* 这样做的好处就是通过mServiceHandler发出的消息都是在HandlerThread中执行
* 所以从这个角度来看,IntentService是可以执行后台任务的
*/
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
/**
* 这个方法里的实现其实就很简单了,就是通过handler发送了一个消息
* 把我们的intent对象和startId发送出去
* 在我们上面的handleMessage()会接收到消息
* 并且通过onHandlerIntent()方法将对象回调给子类
*/
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
/**
* 每次启动IntentService,onStartCommand()就会被调用一次
* 在这个方法里处理每个后台任务的intent
* 可以看到在这个方法里调用的是上方的onStart()方法
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
/**
* 因为looper是无限循环轮询消息的一个机制,所以当我们明确不需要继续使用的话
* 那么我们就应该通过它的quit()方法来终止它的执行
* 这是个编程的好习惯,要记住哦!
*/
@Override
public void onDestroy() {
mServiceLooper.quit();
}
/**
* 这个方法的注释写了:除非你为这个service提供了绑定,否则不需要实现这个方法
* 因为这个方法默认是返回null的
* 所以咱们不用太关注这个方法
*/
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
/**
* 这就是IntentService中定义的抽象方法
* 具体交由它自己的子类来实现
*/
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
源码已经讲得很详细了,这里简单总结下:IntentService是个抽象类,继承自Service,类中有一个mServiceLooper、一个mServiceHandler。在onCreate方法中创建了一个HandlerThread,并且通过HandlerThread获取到了mServiceLooper,最后通过这个mServiceLooper创建了mServiceHandler。mServiceHandler通过消息回调来处理多任务,在handlerMessage方法中有核心代码块:
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);
}
}
源码看到这里,我们已经能答出上面两个问题了。
针对IntentService为何能执行耗时任务?它的实现原理是什么?我们的回答是:IntentService内部是采用Thread+Handler去处理任务的,也即是开启了新的线程,所以它是能执行耗时任务的。
针对IntentService为何执行完任务会自动destroy?我们的回答是:在ServiceHandler的handleMessage方法中,我们看到了stopSelf方法,也就是当任务处理完成时,这里自动调用了stopSelf方法,所以不用开发者主动去stop。