美文网首页Android基础进阶知识
【android】IntentService 使用及源码解析

【android】IntentService 使用及源码解析

作者: 当时不是寻常 | 来源:发表于2018-03-23 10:59 被阅读34次

    本文转载自:https://blog.csdn.net/u011240877/article/details/72972610

    读完本文你将了解:

    在前面两篇文章 源码解读 Android 消息机制( Message MessageQueue Handler Looper)HandlerThread 使用场景及源码解析 中我们了解了 Android 中执行异步任务的两种方式。

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

    IntentService 简介

    一句话总结 IntentService:优先级比较高的、用于串行执行异步任务、会自尽的 Service。

    public abstract class IntentService extends Service {...}
    

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

    由于是一个 Service,IntentService 的优先级比较高,在后台不会轻易被系统杀死;它可以接收 Intent 请求,然后在子线程中按顺序执行。

    官方文档关于它的介绍:

    IntentService 使用工作线程逐一处理所有启动请求。如果你不需要在 Service 中执行并发任务,IntentService 是最好的选择。

    IntentService用法

    IntentService 源码分析

    IntentService 源码很短:

    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() 中将 intent 和 startId 以消息的形式发送到 Handler
    • Handler 中将消息队列中的 Intent 按顺序传递给 onHandleIntent() 方法
    • 在处理完所有启动请求后自动停止服务,不需要我们调用 stopSelf()
    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,如果是就关闭服务。

    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 方法其他都是运行在主线程的。

    IntentService 的使用

    通过前面的源码分析,我们可以看到,最终每个任务的处理都会调用 onHandleIntent(),因此使用 IntentService 也很简单,只需实现 onHandleIntent() 方法,在这里执行对应的后台工作即可。

    举个例子:

    我们写一个使用 IntentService 实现在子线程下载多张 美女图片 的效果。

    创建 IntentService 的子类

    /**
     * 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 中注册!

    布局界面

    <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.

    调用方代码

    /**
     * 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 的方法很相似。

    运行效果

    这里写图片描述

    总结

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

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

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

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

    一句话总结 IntentService:

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

    代码地址

    Thanks

    《Android 开发艺术探索》
    https://developer.android.com/guide/components/services.html#ExtendingIntentService
    http://rainbow702.iteye.com/blog/1143286
    http://blog.csdn.net/javazejian/article/details/52426425

    相关文章

      网友评论

        本文标题:【android】IntentService 使用及源码解析

        本文链接:https://www.haomeiwen.com/subject/tgitcftx.html