HandlerThread 与 IntentService 使用及源码解析
- [HandlerThread 与 IntentService 使用及源码解析](#HandlerThread 与 IntentService 使用及源码解析)
- [1. 阅读前问题卡:HandlerThread 与 IntentService](#1. 阅读前问题卡:HandlerThread 与 IntentService)
- [1.1 阅读前先看这几个问题](#1.1 阅读前先看这几个问题)
- [1.2 读完后完成这 3 个任务](#1.2 读完后完成这 3 个任务)
- [1.2.1 任务 1:拆清 HandlerThread 的工作链路](#1.2.1 任务 1:拆清 HandlerThread 的工作链路)
- [1.2.2 任务 2:说明 IntentService 的串行请求机制](#1.2.2 任务 2:说明 IntentService 的串行请求机制)
- [1.2.3 任务 3:复盘两个下载示例的最小流程](#1.2.3 任务 3:复盘两个下载示例的最小流程)
- [1.3 自检清单](#1.3 自检清单)
- [2. HandlerThread 使用场景及源码解析](#2. HandlerThread 使用场景及源码解析)
- [2.1 导读](#2.1 导读)
- [2.2 HandlerThread 简介](#2.2 HandlerThread 简介)
- [2.3 HandlerThread 源码](#2.3 HandlerThread 源码)
- [2.4 HandlerThread 的使用场景](#2.4 HandlerThread 的使用场景)
- [2.5 举个栗子](#2.5 举个栗子)
- [2.6 运行结果](#2.6 运行结果)
- [2.7 总结](#2.7 总结)
- [3. Android 进阶 16:IntentService 使用及源码解析](#3. Android 进阶 16:IntentService 使用及源码解析)
- [3.1 导读](#3.1 导读)
- [3.2 IntentService 简介](#3.2 IntentService 简介)
- [3.3 IntentService 源码分析](#3.3 IntentService 源码分析)
- [3.4 IntentService 的使用](#3.4 IntentService 的使用)
- [3.4.1 创建 IntentService 的子类](#3.4.1 创建 IntentService 的子类)
- [3.4.2 布局界面](#3.4.2 布局界面)
- [3.4.3 调用方代码](#3.4.3 调用方代码)
- [3.5 运行效果](#3.5 运行效果)
- [3.6 总结](#3.6 总结)
1. 阅读前问题卡:HandlerThread 与 IntentService
建议先读:先读 2.2 和 2.3,弄清 HandlerThread 如何创建 Looper;再读 3.3,看 IntentService 如何基于 HandlerThread 串行处理 Intent。
1.1 阅读前先看这几个问题
HandlerThread要解决什么问题?它相比手写Looper.prepare()、Handler和Looper.loop()省掉了哪部分样板代码?HandlerThread的start()、run()、getLooper()、onLooperPrepared()和Looper.loop()在源码中是什么顺序?getLooper()里wait()和notifyAll()解决的是哪个时机问题?HandlerThread的优先级参数为什么使用android.os.Process的THREAD_PRIORITY_*,而不是java.lang.Thread的优先级?- 下载示例中,
mWorkerHandler、mUIHandler、MessageQueue和handleMessage()分别承担什么角色? IntentService如何通过onCreate()、onStartCommand()、onStart()、ServiceHandler和onHandleIntent()串行处理多个Intent?stopSelf(startId)为什么不会导致第一个任务结束后立刻关闭整个IntentService?- 两个运行截图和
HandlerThread结构图分别验证了哪些界面状态或消息流向?
1.2 读完后完成这 3 个任务
1.2.1 任务 1:拆清 HandlerThread 的工作链路
- 做什么:用 5 到 7 步写出从
DownloadThread.start()到子线程handleMessage()执行任务的链路,并标出Looper、MessageQueue、工作Handler和 UIHandler的位置。 - 验证标准:能解释为什么
getLooper()可能等待,以及为什么下载任务会按消息队列顺序执行。
1.2.2 任务 2:说明 IntentService 的串行请求机制
- 做什么:假设连续调用 3 次
startService(intent),按源码顺序写出这些Intent如何进入ServiceHandler、触发onHandleIntent(),以及何时调用stopSelf(startId)。 - 验证标准:能说明为什么只有最后一次启动请求处理完后服务才会结束,并能指出
onHandleIntent()与其他回调运行线程的差别。
1.2.3 任务 3:复盘两个下载示例的最小流程
- 做什么:对照示例代码和截图,分别复述
HandlerThread示例与IntentService示例中"点击开始下载 -> 子线程执行任务 -> 主线程更新状态或图片"的路径。 - 验证标准:能对应说出截图中的"下载开始信息 / 下载完成信息 / 状态信息 / 开始下载"和代码中的消息类型、按钮点击、UI 更新语句之间的关系。
1.3 自检清单
- 我能一句话说清
HandlerThread是什么,以及它为什么适合多个耗时任务在子线程中串行处理。 - 我能说明
HandlerThread中run()、getLooper()、onLooperPrepared()和Looper.loop()的先后关系。 - 我能区分示例里的工作
Handler和 UIHandler,并说明消息在哪个线程处理。 - 我能说清
IntentService如何借助HandlerThread和ServiceHandler处理启动请求。 - 我能解释
stopSelf(startId)与"最后一个任务执行完才关闭服务"的关系。 - 我能根据示例或截图复述从开始下载到状态更新的最小路径。
2. HandlerThread 使用场景及源码解析
2.1 导读
- HandlerThread 简介
- HandlerThread 源码
- HandlerThread 的使用场景
- 举个栗子
- 运行结果
- 总结
- 代码地址
为了避免 ANR,我们常常需要在线程中做耗时操作,然后把结果抛到主线程进行处理。
Android 提供了多种用于这种场景的组件,其中一种就是本篇文章要介绍的 HandlerThread。
2.2 HandlerThread 简介
源码解读 Android 消息机制(Message、MessageQueue、Handler、Looper)中我们介绍了,Handler 必须要和 Looper 结合使用,尤其在子线程中创建 Handler 的话,需要这样写:
java
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// 这里处理消息
}
};
Looper.loop();
}
可以看到,非常繁琐,一层套一层看着也不美观。
HandlerThread 就是为了帮我们免去写上面那样的代码而生的。
官方文档对它的介绍:
HandlerThread是一个包含Looper的Thread,我们可以直接使用这个Looper创建Handler。
有这么神奇?去瞅瞅它的源码。
2.3 HandlerThread 源码
HandlerThread 源码非常简单,看起来 so easy:
java
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
//也可以指定线程的优先级,注意使用的是 android.os.Process 而不是 java.lang.Thread 的优先级!
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
// 子类需要重写的方法,在这里做一些执行前的初始化工作
protected void onLooperPrepared() {
}
//获取当前线程的 Looper
//如果线程不是正常运行的就返回 null
//如果线程启动后,Looper 还没创建,就 wait() 等待 创建 Looper 后 notify
public Looper getLooper() {
if (!isAlive()) {
return null;
}
synchronized (this) {
while (isAlive() && mLooper == null) { //循环等待
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
//调用 start() 后就会执行的 run()
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare(); //帮我们创建了 Looepr
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll(); //Looper 已经创建,唤醒阻塞在获取 Looper 的线程
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop(); //开始循环
mTid = -1;
}
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
public int getThreadId() {
return mTid;
}
}
可以看到,① HandlerThread 本质还是个 Thread,创建后别忘了调用 start()。
② 在 run() 方法中创建了 Looper,调用 onLooperPrepared() 后开启了循环。
③ 我们要做的就是在子类中重写 onLooperPrepared(),做一些初始化工作。
④ 在创建 HandlerThread 时可以指定优先级,注意这里的参数是 Process.XXX 而不是 Thread.XXX。
Process.setThreadPriority(int)- A Linux priority level, from -20 for highest scheduling priority to 19 for lowest scheduling priority.
可选的值如下:
java
public static final int THREAD_PRIORITY_DEFAULT = 0;
public static final int THREAD_PRIORITY_LOWEST = 19;
public static final int THREAD_PRIORITY_BACKGROUND = 10;
public static final int THREAD_PRIORITY_FOREGROUND = -2;
public static final int THREAD_PRIORITY_DISPLAY = -4;
public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8;
public static final int THREAD_PRIORITY_AUDIO = -16;
2.4 HandlerThread 的使用场景
我们知道,HandlerThread 所做的就是在新开的子线程中创建了 Looper,那它的使用场景就是 Thread + Looper 使用场景的结合,即:在子线程中执行耗时的、可能有多个任务的操作。
比如说多个网络请求操作,或者多文件 I/O 等等。
使用 HandlerThread 的典型例子就是 IntentService,我们下篇文章介绍它。
2.5 举个栗子
我们写一个使用 HandlerThread 实现子线程完成多个下载任务的 demo。
先创建一个 HandlerThread 子类,它有两个 Handler 类型的成员变量,一个是用于在子线程传递、执行任务,另一个用于外部传入,在主线程显示下载状态:
java
/**
* Description:
* <br> 继承 HandlerThread 模拟下载线程
* <p>
* <br> Created by shixinzhang on 17/6/7.
* <p>
* <br> Email: shixinzhang2016@gmail.com
* <p>
* <a href="https://about.me/shixinzhang">About me</a>
*/
public class DownloadThread extends HandlerThread implements Handler.Callback {
private final String TAG = this.getClass().getSimpleName();
private final String KEY_URL = "url";
public static final int TYPE_START = 1;
public static final int TYPE_FINISHED = 2;
private Handler mWorkerHandler;
private Handler mUIHandler;
private List<String> mDownloadUrlList;
public DownloadThread(final String name) {
super(name);
}
@Override
protected void onLooperPrepared() { //执行初始化任务
super.onLooperPrepared();
mWorkerHandler = new Handler(getLooper(), this); //使用子线程中的 Looper
if (mUIHandler == null) {
throw new IllegalArgumentException("Not set UIHandler!");
}
//将接收到的任务消息挨个添加到消息队列中
for (String url : mDownloadUrlList) {
Message message = mWorkerHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putString(KEY_URL, url);
message.setData(bundle);
mWorkerHandler.sendMessage(message);
}
}
public void setDownloadUrls(String... urls) {
mDownloadUrlList = Arrays.asList(urls);
}
public Handler getUIHandler() {
return mUIHandler;
}
//注入主线程 Handler
public DownloadThread setUIHandler(final Handler UIHandler) {
mUIHandler = UIHandler;
return this;
}
/**
* 子线程中执行任务,完成后发送消息到主线程
*
* @param msg
* @return
*/
@Override
public boolean handleMessage(final Message msg) {
if (msg == null || msg.getData() == null) {
return false;
}
String url = (String) msg.getData().get(KEY_URL);
//下载开始,通知主线程
Message startMsg = mUIHandler.obtainMessage(TYPE_START, "\n 开始下载 @" + DateUtils.getCurrentTime() + "\n" + url);
mUIHandler.sendMessage(startMsg);
SystemClock.sleep(2000); //模拟下载
//下载完成,通知主线程
Message finishMsg = mUIHandler.obtainMessage(TYPE_FINISHED, "\n 下载完成 @" + DateUtils.getCurrentTime() + "\n" + url);
mUIHandler.sendMessage(finishMsg);
return true;
}
@Override
public boolean quitSafely() {
mUIHandler = null;
return super.quitSafely();
}
}
可以看到,DownloadThread 中做了以下工作:
- 创建一个子线程
Handler - 然后在
onLooperPrepared()中初始化Handler,使用的是HandlerThread创建的Looper - 同时将外部传入的下载 URL 以
Message的方式发送到子线程中的MessageQueue中 - 这样当调用
DownloadThread.start()时,子线程中的Looper开始工作,会按顺序取出消息队列中的队列处理,然后调用子线程的Handler处理 - 也就是上面的
handleMessage()方法,在这个方法中进行耗时任务 - 然后通过
mUIHandler将下载状态信息传递到主线程
调用 Activity 的代码:
java
/**
* Description:
* <br> HandlerThread 示例程序
* <p>
* <br> Created by shixinzhang on 17/6/7.
* <p>
* <br> Email: shixinzhang2016@gmail.com
* <p>
* <a href="https://about.me/shixinzhang">About me</a>
*/
public class HandlerThreadActivity extends AppCompatActivity implements Handler.Callback {
@BindView(R.id.tv_start_msg)
TextView mTvStartMsg;
@BindView(R.id.tv_finish_msg)
TextView mTvFinishMsg;
@BindView(R.id.btn_start_download)
Button mBtnStartDownload;
private Handler mUIHandler;
private DownloadThread mDownloadThread;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread_test);
ButterKnife.bind(this);
init();
}
private void init() {
mUIHandler = new Handler(this);
mDownloadThread = new DownloadThread("下载线程");
mDownloadThread.setUIHandler(mUIHandler);
mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ",
"http://bbs.005.tv/thread-589833-1-1.html", "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?");
}
@OnClick(R.id.btn_start_download)
public void startDownload() {
mDownloadThread.start();
mBtnStartDownload.setText("正在下载");
mBtnStartDownload.setEnabled(false);
}
//主线程中的 Handler 处理消息的方法
@Override
public boolean handleMessage(final Message msg) {
switch (msg.what) {
case DownloadThread.TYPE_FINISHED:
mTvFinishMsg.setText(mTvFinishMsg.getText().toString() + "\n " + msg.obj);
break;
case DownloadThread.TYPE_START:
mTvStartMsg.setText(mTvStartMsg.getText().toString() + "\n " + msg.obj);
break;
}
return true;
}
}
布局文件:
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/tv_start_msg"
android:layout_width="match_parent"
android:layout_height="200dp"
android:text="下载开始信息"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorAccent"/>
<TextView
android:id="@+id/tv_finish_msg"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="8dp"
android:text="下载完成信息"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorAccent"/>
<Button
android:id="@+id/btn_start_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="开始下载"/>
</LinearLayout>
重点是 init() 方法(其中有福利,你懂得):
java
private void init() {
mUIHandler = new Handler(this);
mDownloadThread = new DownloadThread("下载线程");
mDownloadThread.setUIHandler(mUIHandler);
mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ",
"http://bbs.005.tv/thread-589833-1-1.html", "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?");
}
在这个方法中我们创建一个 DownloadThread,也就是 HandlerThread,然后传入 UI 线程中的 Handler。
最后在按钮的点击事件中调用了 start() 方法。
2.6 运行结果
2.7 总结
上面的例子中 HandlerThread 配合一个主线程 Handler 完成了在子线程中串行执行任务,同时在主线程中反馈状态的功能。
如果用一句话总结 HandlerThread 的特点:
它就是一个帮我们创建 Looper 的线程,让我们可以直接在线程中使用 Handler 来处理异步任务。
3. Android 进阶 16:IntentService 使用及源码解析
3.1 导读
- IntentService 简介
- IntentService 源码分析
- IntentService 的使用
- 创建 IntentService 的子类
- 布局界面
- 调用方代码
- 运行效果
- 总结
- 代码地址
- Thanks
在前面两篇文章《源码解读 Android 消息机制(Message、MessageQueue、Handler、Looper)》和《HandlerThread 使用场景及源码解析》中,我们了解了 Android 中执行异步任务的两种方式。
本篇文章介绍另外一种:IntentService。
3.2 IntentService 简介
java
public abstract class IntentService extends Service {...}
IntentService 是一个抽象类,继承了 Service。
由于是一个 Service,IntentService 的优先级比较高,在后台不会轻易被系统杀死;它可以接收 Intent 请求,然后在子线程中按顺序执行。
官方文档关于它的介绍:
IntentService使用工作线程逐一处理所有启动请求。如果你不需要在Service中执行并发任务,IntentService是最好的选择。
3.3 IntentService 源码分析
IntentService 源码很短:
java
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
//内部创建的 Handler
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);
}
}
//子类需要重写的构造函数,参数是服务的名称
public IntentService(String name) {
super();
mName = name;
}
//设置当前服务被意外关闭后是否重新
//如果设置为 true,onStartCommand() 方法将返回 Service.START_REDELIVER_INTENT,这样当
//当前进程在 onHandleIntent() 方法返回前销毁时,会重启进程,重新使用之前的 Intent 启动这个服务
//(如果有多个 Intent,只会使用最后的一个)
//如果设置为 false,onStartCommand() 方法返回 Service.START_NOT_STICKY,当进程销毁后也不重启服务
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
//创建时启动一个 HandlerThread
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//拿到 HandlerThread 中的 Looper,然后创建一个子线程中的 Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
//将 intent 和 startId 以消息的形式发送到 Handler
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit(); //值得学习的,在销毁时退出 Looper
}
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
从上述代码可以看到,IntentService 做了以下工作:
- 创建了一个
HandlerThread默认的工作线程 - 使用
HandlerThread的Looper创建了一个Handler,这个Handler执行在子线程 - 在
onStartCommand()中调用onStart(),然后在onStart()中将intent和startId以消息的形式发送到Handler - 在
Handler中将消息队列中的Intent按顺序传递给onHandleIntent()方法 - 在处理完所有启动请求后自动停止服务,不需要我们调用
stopSelf()
java
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
有同学可能有疑问,在 handleMessage() 方法中不是调用了一次 onHandleIntent() 后就调用 stopSelf() 了吗,这不是只能执行一个任务么?
仔细看下可以发现,这个 stopSelf() 方法传递了一个 id,这个 id 是启动服务时 IActivityManager 分配的 id,当我们调用 stopSelf(id) 方法结束服务时,IActivityManager 会对比当前 id 是否为最新启动该服务的 id,如果是就关闭服务。
java
public final void stopSelf(int startId) {
if (mActivityManager == null) {
return;
}
try {
mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
}
因此只有当最后一次启动 IntentService 的任务执行完毕才会关闭这个服务。
此外还要注意的是,IntentService 中除了 onHandleIntent() 方法,其他都是运行在主线程的。
3.4 IntentService 的使用
通过前面的源码分析,我们可以看到,最终每个任务的处理都会调用 onHandleIntent(),因此使用 IntentService 也很简单,只需实现 onHandleIntent() 方法,在这里执行对应的后台工作即可。
举个例子:
我们写一个使用 IntentService 实现在子线程下载多张 美女图片 的效果。
3.4.1 创建 IntentService 的子类
java
/**
* Description:
* <br> 使用 IntentService 实现下载
* <p>
* <br> Created by shixinzhang on 17/6/8.
* <p>
* <br> Email: shixinzhang2016@gmail.com
* <p>
* <a href="https://about.me/shixinzhang">About me</a>
*/
public class DownloadService extends IntentService {
private static final String TAG = "DownloadService";
public static final String DOWNLOAD_URL = "down_load_url";
public static final int WHAT_DOWNLOAD_FINISHED = 1;
public static final int WHAT_DOWNLOAD_STARTED = 2;
public DownloadService() {
super(TAG);
}
private static Handler mUIHandler;
public static void setUIHandler(final Handler UIHandler) {
mUIHandler = UIHandler;
}
/**
* 这个方法运行在子线程
*
* @param intent
*/
@Override
protected void onHandleIntent(final Intent intent) {
String url = intent.getStringExtra(DOWNLOAD_URL);
if (!TextUtils.isEmpty(url)) {
sendMessageToMainThread(WHAT_DOWNLOAD_STARTED, "\n " + DateUtils.getCurrentTime() + " 开始下载任务:\n" + url);
try {
Bitmap bitmap = downloadUrlToBitmap(url);
SystemClock.sleep(1000); //延迟一秒发送消息
sendMessageToMainThread(WHAT_DOWNLOAD_FINISHED, bitmap);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 发送消息到主线程
*
* @param id
* @param o
*/
private void sendMessageToMainThread(final int id, final Object o) {
if (mUIHandler != null) {
mUIHandler.sendMessage(mUIHandler.obtainMessage(id, o));
}
}
/**
* 下载图片
*
* @param url
* @return
* @throws Exception
*/
private Bitmap downloadUrlToBitmap(String url) throws Exception {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
Bitmap bitmap = BitmapFactory.decodeStream(in);
urlConnection.disconnect();
in.close();
return bitmap;
}
}
在上面的代码中,我们做了以下几件事:
- 在
onHandleIntent()中接收任务,开始下载,同时将状态返回给主线程 - 下载完成后将得到的 Bitmap 通过
Handler发送到主线程
为了界面上有明显效果,设置了一定延时。
IntentService也是Service,别忘了在AndroidManifest中注册!
3.4.2 布局界面
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="@+id/iv_display"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:id="@+id/tv_status"
android:layout_width="match_parent"
android:layout_height="250dp"
android:padding="8dp"
android:text="状态信息:"/>
<Button
android:id="@+id/btn_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始下载"/>
</LinearLayout>
界面上有一个开始下载按钮,一个显示下载状态的 TextView,一个展示图片的 ImageView。
3.4.3 调用方代码
java
/**
* Description:
* <br> IntentService 实例
* <p>
* <br> Created by shixinzhang on 17/6/9.
* <p>
* <br> Email: shixinzhang2016@gmail.com
* <p>
* <a href="https://about.me/shixinzhang">About me</a>
*/
public class IntentServiceActivity extends AppCompatActivity implements Handler.Callback {
@BindView(R.id.iv_display)
ImageView mIvDisplay;
@BindView(R.id.btn_download)
Button mBtnDownload;
@BindView(R.id.tv_status)
TextView mTvStatus;
private List<String> urlList = Arrays.asList("https://ws1.sinaimg.cn/large/610dc034ly1fgepc1lpvfj20u011i0wv.jpg",
"https://ws1.sinaimg.cn/large/d23c7564ly1fg6qckyqxkj20u00zmaf1.jpg",
"https://ws1.sinaimg.cn/large/610dc034ly1fgchgnfn7dj20u00uvgnj.jpg"); //美女图片地址
int mFinishCount; //完成的任务个数
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_intent_service);
ButterKnife.bind(this);
DownloadService.setUIHandler(new Handler(this));
}
@OnClick(R.id.btn_download)
public void downloadImage() {
Intent intent = new Intent(this, DownloadService.class);
for (String url : urlList) {
intent.putExtra(DownloadService.DOWNLOAD_URL, url);
startService(intent);
}
mBtnDownload.setEnabled(false);
}
@Override
public boolean handleMessage(final Message msg) {
if (msg != null) {
switch (msg.what) {
case DownloadService.WHAT_DOWNLOAD_FINISHED:
mIvDisplay.setImageBitmap((Bitmap) msg.obj);
mBtnDownload.setText("完成 " + (++mFinishCount) + "个任务");
break;
case DownloadService.WHAT_DOWNLOAD_STARTED:
mTvStatus.setText(mTvStatus.getText() + (String) msg.obj);
break;
}
}
return true;
}
}
Activity 中做了以下几件事:
- 设置 UI 线程的
Handler给IntentService - 使用
startService(intent)启动IntentService执行图片下载任务 - 在
Handler的handleMessage()中根据消息类型进行相应处理
可以看到,调用方的代码和上一篇使用 HandlerThread 的方法很相似。
3.5 运行效果
3.6 总结
本篇文章介绍了 IntentService 的使用和源码。
在第一次启动 IntentService 后,IntentService 仍然可以接受新的请求,接受到的新的请求被放入了工作队列中,等待被串行执行。
使用
IntentService显著简化了启动服务的实现,如果您决定还重写其他回调方法(如onCreate()、onStartCommand()或onDestroy()),请确保调用超类实现,以便IntentService能够妥善处理工作线程的生命周期。
由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。
一句话总结 IntentService:
优先级比较高的、用于串行执行异步任务、会自尽的 Service。