HandlerThread 与 IntentService 使用及源码解析


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 阅读前先看这几个问题

  1. HandlerThread 要解决什么问题?它相比手写 Looper.prepare()HandlerLooper.loop() 省掉了哪部分样板代码?
  2. HandlerThreadstart()run()getLooper()onLooperPrepared()Looper.loop() 在源码中是什么顺序?
  3. getLooper()wait()notifyAll() 解决的是哪个时机问题?
  4. HandlerThread 的优先级参数为什么使用 android.os.ProcessTHREAD_PRIORITY_*,而不是 java.lang.Thread 的优先级?
  5. 下载示例中,mWorkerHandlermUIHandlerMessageQueuehandleMessage() 分别承担什么角色?
  6. IntentService 如何通过 onCreate()onStartCommand()onStart()ServiceHandleronHandleIntent() 串行处理多个 Intent
  7. stopSelf(startId) 为什么不会导致第一个任务结束后立刻关闭整个 IntentService
  8. 两个运行截图和 HandlerThread 结构图分别验证了哪些界面状态或消息流向?

1.2 读完后完成这 3 个任务


1.2.1 任务 1:拆清 HandlerThread 的工作链路
  • 做什么:用 5 到 7 步写出从 DownloadThread.start() 到子线程 handleMessage() 执行任务的链路,并标出 LooperMessageQueue、工作 Handler 和 UI Handler 的位置。
  • 验证标准:能解释为什么 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 是什么,以及它为什么适合多个耗时任务在子线程中串行处理。
  • 我能说明 HandlerThreadrun()getLooper()onLooperPrepared()Looper.loop() 的先后关系。
  • 我能区分示例里的工作 Handler 和 UI Handler,并说明消息在哪个线程处理。
  • 我能说清 IntentService 如何借助 HandlerThreadServiceHandler 处理启动请求。
  • 我能解释 stopSelf(startId) 与"最后一个任务执行完才关闭服务"的关系。
  • 我能根据示例或截图复述从开始下载到状态更新的最小路径。

2. HandlerThread 使用场景及源码解析


2.1 导读

  • HandlerThread 简介
  • HandlerThread 源码
  • HandlerThread 的使用场景
  • 举个栗子
  • 运行结果
  • 总结
  • 代码地址

为了避免 ANR,我们常常需要在线程中做耗时操作,然后把结果抛到主线程进行处理。

Android 提供了多种用于这种场景的组件,其中一种就是本篇文章要介绍的 HandlerThread


2.2 HandlerThread 简介

源码解读 Android 消息机制(MessageMessageQueueHandlerLooper)中我们介绍了,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 是一个包含 LooperThread,我们可以直接使用这个 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 消息机制(MessageMessageQueueHandlerLooper)》和《HandlerThread 使用场景及源码解析》中,我们了解了 Android 中执行异步任务的两种方式。

本篇文章介绍另外一种:IntentService


3.2 IntentService 简介

java 复制代码
public abstract class IntentService extends Service {...}

IntentService 是一个抽象类,继承了 Service

由于是一个 ServiceIntentService 的优先级比较高,在后台不会轻易被系统杀死;它可以接收 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 默认的工作线程
  • 使用 HandlerThreadLooper 创建了一个 Handler,这个 Handler 执行在子线程
  • onStartCommand() 中调用 onStart(),然后在 onStart() 中将 intentstartId 以消息的形式发送到 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 线程的 HandlerIntentService
  • 使用 startService(intent) 启动 IntentService 执行图片下载任务
  • HandlerhandleMessage() 中根据消息类型进行相应处理

可以看到,调用方的代码和上一篇使用 HandlerThread 的方法很相似。


3.5 运行效果


3.6 总结

本篇文章介绍了 IntentService 的使用和源码。

在第一次启动 IntentService 后,IntentService 仍然可以接受新的请求,接受到的新的请求被放入了工作队列中,等待被串行执行。

使用 IntentService 显著简化了启动服务的实现,如果您决定还重写其他回调方法(如 onCreate()onStartCommand()onDestroy()),请确保调用超类实现,以便 IntentService 能够妥善处理工作线程的生命周期。

由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。

一句话总结 IntentService

优先级比较高的、用于串行执行异步任务、会自尽的 Service

相关推荐
我命由我1234511 小时前
Android Studio - Android Studio 自定义预览尺寸
android·java·ide·java-ee·android studio·android-studio·android runtime
取个名字太难了~13 小时前
越用越便宜,越用越强大:影像 SDK 的边际成本递减与网络效应实践
android·数码相机·美颜·相机连接·demu
AFinalStone17 小时前
Android 7系统网络(四)Native层(下)—netd Controller详解
android·网络
爱笑鱼17 小时前
Handler(一):post 之后,Runnable 到底在哪个线程执行?
android
木木子2218 小时前
[特殊字符] 音乐播放器——状态驱动的多媒体控制
android·开发语言·华为·php·harmonyos
雨白18 小时前
C 语言基础:结构体、联合体与枚举
android
阿巴斯甜19 小时前
Android 代码混淆
android
apihz21 小时前
台风实时与历史详情查询免费 API 接口完整教程
android·开发语言·tcp/ip·dubbo·台风·天气预报
浪客川1 天前
AOSP源码隐藏状态栏
android·aosp