IntentService——Handler与Service的结合_handle 向 service 推送消息-程序员宅基地

技术标签: handler  service  android  Android源码分析  

综述

  我们都知道Service是作为后台服务运行再程序中的。但是Service他依然是运行在主线程中的,所以我们依然不能在Service中进行耗时的操作。所以当我们在Service处理时,我们需要在Service中开启一个子线程,并且在子线程中运行。当然为了简化我们的操作,在Android中为我们提供了IntentService来进行这一处理,下面我们就来看一下这个IntentService用法以及它的工作原理。

用法简介

  IntentService它继承自Service,一来说我们开启一个Service可以通过startService和bindService两个方式进行开启一个服务,但是对于IntentService我们采用startService方法进行开启服务,对于为什么要这么做,在后面会进行分析讲解。下面我们来看一下如何使用这个IntentService的。

效果演示

  在这里我们做一个倒计时的程序,以毫秒为单位。这里先看一下效果演示。
这里写图片描述

代码分析

  在这里我们使用到了开源框架EventBus,对于EventBus的使用可以参考 EventBus3.0使用详解这篇文章。由于我们用到这EventBus,首先我们创建一个实体类,在EventBus中进行发送,接收处理。

package com.example.ljd.intentservice;

public class Counter {

    public Counter(int progress,int tag) {
        this.progress = progress;
        this.tag = tag;
    }

    public int progress;      //进度显示

    public int tag;           //TextView的Tag
}

  下面我们看一下IntentService中的代码。

package com.example.ljd.intentservice;

import android.app.IntentService;
import android.content.Intent;
import android.content.Context;

import org.greenrobot.eventbus.EventBus;


public class MyIntentService extends IntentService {
    

    private static final String ACTION_COUNTER = "com.example.ljd.intentservice.action.COUNTER";

    private static final String EXTRA_SEC = "com.example.ljd.intentservice.extra.SECOND";

    private static final String EXTRA_TAG = "com.example.ljd.intentservice.extra.TAG";

    private static final int SLEEP_TIME = 1;

    public MyIntentService() {
        super("MyIntentService");
    }


    public static void startDownload(Context context, int second,int tag) {
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_COUNTER);
        intent.putExtra(EXTRA_SEC, second);
        intent.putExtra(EXTRA_TAG,tag);
        context.startService(intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_COUNTER.equals(action)) {
                final int second = intent.getIntExtra(EXTRA_SEC, 0);
                final int tag = intent.getIntExtra(EXTRA_TAG,0);
                handleActionFoo(second,tag);
            }
        }
    }

    private void handleActionFoo(int sec,int tag) {
        int millis = sec * 1000;
        Counter counter = new Counter(0,tag);

        for (int i = millis;i >= 0; i-=SLEEP_TIME){
            counter.progress = i;
            EventBus.getDefault().post(counter);
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

  在上面的handleActionFoo方法中进行我们的耗时任务。然后我们在看一下Activity中的代码。

package com.example.ljd.intentservice;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.util.Random;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    

    private int mTextViewTag;
    private Button mAddButton;
    private LinearLayout mContainerLinear;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().register(this);
        mTextViewTag = 0;
        mAddButton = (Button) findViewById(R.id.add_btn);
        mContainerLinear = (LinearLayout) findViewById(R.id.linear_container);
        mAddButton.setOnClickListener(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void OnEventProgress(Counter counter){
        TextView textView = (TextView)mContainerLinear.findViewWithTag(counter.tag);
        textView.setText(counter.progress + "ms");
    }

    @Override
    protected void onDestroy() {
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.add_btn:
                //生成1~3之间的随机数
                Random random = new Random();
                int num = random.nextInt(3)%(3) + 1;

                TextView textView = new TextView(this);
                textView.setTag(mTextViewTag);
                textView.setText(num * 1000 + "ms");
                mContainerLinear.addView(textView);
                MyIntentService.startDownload(this,num,mTextViewTag);
                mTextViewTag++;
                break;
            default:
                break;
        }
    }
}

  最后是布局代码。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.ljd.intentservice.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/add_btn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="add"/>
    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:id="@+id/linear_container"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </LinearLayout>
    </ScrollView>

</LinearLayout>

  对于上面代码实现起来都是非常的简单,在这里就不在进行详细介绍。

IntentService工作原理分析

  其实对于IntentService的工作原理也不复杂,既然在IntentService中能够进行耗时操作,也就是说在这个IntentService中必然也创建了一个子线程,在Android中我们称为工作者线程。然后在这个工作者线程中进行我们的任务。在分析IntentService之前,我们先看一下HandlerThread。

HandlerThread

  其实HandlerThread就是一个工作者线程,在这里看一下HandlerThread的源码。

package android.os;

public class HandlerThread extends Thread {
    
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    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;
    }
}

  看过上篇文章 Android的消息机制——Handler的工作过程就很容易理解这个HandlerThread了。还记的我们在上篇文章的最后,新建了一个包含Looper的子线程。而这个HandlerThread也就是一个包含Looper的子线程。所以当我们需要创建一个包含Looper的线程时直接使用HandlerThread即可。对于HandlerThread有以下几点需要说明一下。
  1. 在构造方法中设置线程优先级的时候,使用的Process是android.os包中的而不是java.lang包内的。
  2. 如果在Looper开启消息循环之前我们进行一些设置,我们可以继承HandlerThread并且重写onLooperPrepared方法。
  3. 通过getLooper方法我们获取HandlerThread的Looper对象时,有可能Looper还未创建完成。所以在getLooper中未创建Looper是进行了线程等待操作,在创建完Looper以后在返回Looper对象。

IntentService

  下面我们再看一下IntentService。

package android.app;

import android.annotation.WorkerThread;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;

public abstract class IntentService extends Service {
    
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    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;
    }

    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @WorkerThread
    protected abstract void onHandleIntent(Intent intent);
}

  我们看一下这个IntentService的构造是不是很简单。在这里主要看一下onCreate和onStart方法即可。在onCreate中,我们开启了一个HandlerThread线程,之后获取HandlerThread线程中的Looper,并通过这个Looper创建了一个Handler。然后在onStart方法中通过这个Handler将intent与startId作为Message的参数进行发送到消息队列中,然后交由Handler中的handleMessage中进行处理。由于在onStart方法是在主线程内运行的,而Handler是通过工作者线程HandlerThread中的Looper创建的。所以也就是在主线程中发送消息,在工作者接收到消息后便可以进行一些耗时的操作。
  我们在看一下handleMessage中的操作,在handleMessage中调用onHandleIntent方法,他是一个抽象方法,所以在我们的Service中复写onHandleIntent方法并且将耗时的操作写在onHandleIntent方法内即可。当执行完onHandleIntent后通过stopSelf来停止服务,这样就不用我们手动停止服务了。所以也就回答了我们上面那个为什么要使用startService而不用onBind来开启一个IntentService。

总结

  从我们的示例和源码分析中可以看出来。对于通过IntentService来执行任务,他是串行的。也就是说只有在上一个任务执行完以后才会执行下一个任务。因为Handler中将消息插入消息队列,而队列又是先进先出的数据结构。所以只有在上个任务执行完成以后才能够获取到下一个任务进行操作。在这里也就说明了对于高并发的任务同过IntentService是不合适。

源码下载

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ljd2038/article/details/50922853

智能推荐

动态更新阿里云DDNS解析记录的IPv6地址,随时随地用域名远程访问自己的电脑【如何远程访问家里的电脑】_阿里云ipv6动态域名解析-程序员宅基地

文章浏览阅读1.3w次,点赞15次,收藏85次。本文详细描述了如何利用IPv6+域名解析实现远程访问自己的电脑_阿里云ipv6动态域名解析

使用 Swift iOS 15+ 将经过微调的人工智能与 OpenAI 集成和利用:聊天机器人教程_ios 集成openai-程序员宅基地

文章浏览阅读540次。欢迎来到使用强大的编程语言 Swift 以及 UIKit 框架和 OpenAI 令人难以置信的 Davinci 人工智能模型训练您自己的聊天机器人模型的激动人心的旅程。在这个循序渐进的教程中,我们将深入 AI 和应用程序开发的世界,结合尖端技术来训练能够进行有意义对话的聊天机器人模型。想象一下,拥有一个可以理解和响应自然语言的聊天机器人模型,为用户提供个性化和智能的交互。借助 Swift 和 Davinci 模型,我们拥有实现这一目标的完美工具。_ios 集成openai

CentOS安装MySQL8详细步骤-程序员宅基地

文章浏览阅读2w次,点赞10次,收藏70次。MySQL8安装详细步骤_centos安装mysql8

【Struts2】实现用户登录与登录验证简单示例_struts2登录验证-程序员宅基地

文章浏览阅读1.4k次。一、Struts2相关文件配置1.导入jar包:2.配置web.xml文件:<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> &_struts2登录验证

安卓蓝牙问题一般分析步骤_bluetoothphonepolicy-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏26次。BT问题解决步骤确认问题:需要分清模块(Audio,BT,Power等)声音类问题的大部分是Audio模块的;功耗类问题,优先要求功耗的负责人先进行分析;本地复现:(必现或高概率问题一定要本地复现下)确认复现步骤,排除测试步骤问题(误操作或测试用例不对);对比验证:(必现或高概率问题,最好本地对比验证下)更换对比机,同类型三方apk,车载设备,连接设备;Check Log:需要check测试提供的有效性分析问题以APLog和HCILog为主,log时间点和问题复现需要对应,log_bluetoothphonepolicy

pip异常解决办法 Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/usr_gym could not install packages due to an environme-程序员宅基地

文章浏览阅读3.4k次。使用 pip install gym 命令出现以下错误:##Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: ‘/usr/local/lib/python2.7/dist-packages/_markupbase’Consider using the --user optio..._gym could not install packages due to an environmenterror:

随便推点

全局变量和局部变量_说明局部变量在哪个文件中声明,在哪个文件中给全局变量中赋初值,并举例说明一个全-程序员宅基地

文章浏览阅读1.6k次。C语言_说明局部变量在哪个文件中声明,在哪个文件中给全局变量中赋初值,并举例说明一个全

【20090603-02】地图投影的选择(转载)http://blog.csdn.net/chenyq2008/archive/2007/12/04/1915918.aspx...-程序员宅基地

文章浏览阅读92次。地图投影的选择选择投影的目的在于使所选投影的性质、特点适合于地图的用途,同时考虑地图在图廓范围内变形较小而且变形分布均匀。海域使用的地图多采用保角投影,因其能保持方位角度的正确。我国的基本比例尺地形图(1:5千,1:1万,1:2.5万,1:5万,1:10万,1:25万,1:50万,1:100万)中,大于等于50万的均采用高斯-克吕格投影(Gauss-Kruger),这是一个等角横切椭圆柱投影,..._墨卡托投影标准纬线之前长度缩小,标准纬线之外长度放大

微生物分子生态学研究方法培训通知(禇海燕/冯友智/陈瑞蕊/蔡元锋/叶茂等)-程序员宅基地

文章浏览阅读1.0k次。各有关单位:为进一步提高并推动高等院校、科研院所及企事业单位在生态环境科学研究中的技术应用水平。“农环视界”公众号组织邀请本领域知名专家定期举办“生态环境科学研究先进技术培训”。通过课程培训提高学科实验、实践技术应用水平并掌握相关先进技术方法。老师讲课会结合实际案例进行内容解析,并配备实操单元进行针对性指导,解决实际问题,拓宽学员的理论知识、提升学员的实际操作能力及相关经验。本期主题:微生物分子..._禇海燕二级教授

linus开启snmp_Linux配置snmp-程序员宅基地

文章浏览阅读264次。机器环境[root@linux-node1 ~]# cat /etc/redhat-releaseCentOS Linux release 7.1.1503 (Core)[root@linux-node1 ~]# hostnamelinux-node1.nmap.com[root@linux-node1 ~]#安装net-snmp等工具[root@linux-node1 ~]# yum insta..._net-snmp-utils-5.7.2-24.el7_2.1x86

使用Hammerspoon打造你的个性化 macOS 桌面:一个强大的自动化工具-程序员宅基地

文章浏览阅读346次,点赞3次,收藏5次。使用Hammerspoon打造你的个性化 macOS 桌面:一个强大的自动化工具项目地址:https://gitcode.com/zuorn/hammerspoon_configHammerspoon 是一款针对macOS的强大扩展工具,它允许用户通过 Lua 脚本来控制和定制操作系统的行为。这个项目提供了一个预配置的 Hammerspoon 配置集合,旨在帮助新手快速上手并体验到 Hamme..._macos 桌面程序自动化

Web逆向、软件逆向、安卓逆向、APP逆向,关于网络安全这些你必须懂_网络安全逆向-程序员宅基地

文章浏览阅读906次,点赞21次,收藏11次。为了帮助大家更好的学习网络安全,小编给大家准备了一份网络安全入门/进阶学习资料,里面的内容都是适合零基础小白的笔记和资料,不懂编程也能听懂、看懂,所有资料共282G,朋友们如果有需要全套网络安全入门+进阶学习资源包,可以点击免费领取(如遇扫码问题,可以在评论区留言领取哦)~有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取CSDN大礼包:全网最全《网络安全入门&进阶学习资源包》免费分享**(安全链接,放心点击)**​。