Android中的线程形态(二)(HandlerThread/IntentService)

本篇提纲(二).png

一.HandlerThread的使用与原理解析

  HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,它不仅建立了一个线程,并且创立了消息队列,有自己的looper,可以让我们在自己的线程中分发和处理消息,并对外提供自己这个Looper对象的get方法。
  HandlerThread自带Looper使他可以通过消息队列,来重复使用当前线程,节省系统资源开销。这是它的优点也是缺点,每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。

1.HandlerThread使用步骤

第一步:创建HandlerThread实例对象

HandlerThread handlerThread = new HandlerThread("myThread");

第二步:启动HandlerThread线程

handlerThread.start();

第三步:构建循环消息处理机制

 private Handler.Callback mSubCallback = new Handler.Callback() {
        //该接口的实现就是处理异步耗时任务的,因此该方法执行在子线程中
        @Override
        public boolean handleMessage(Message msg) {
                //doSomething  处理异步耗时任务的
                mUIHandler.sendMessage(msg1);   //向UI线程发送消息,用于更新UI等操作
        }
    };

第四步:构建子线程中的Handler:

//由于这里已经获取了workHandle.getLooper(),因此这个Handler是在HandlerThread线程也就是子线程中
childHandler = new Handler(handlerThread.getLooper(), mSubCallback);
button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        //发送异步耗时任务到HandlerThread中,也就是上面的mSubCallback中
        childHandler.sendMessage(msg);
    }
});

第五步:构建UI线程Handler处理消息(如果需要更新UI的话有这一步)

    private Handler mUIHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //在UI线程中做的事情,比如设置图片什么的
        }
    };

2.HandlerThread的实现原理

  HandlerThread的实现原理比较简单,源码也比较少(只有150行代码),首先它是继承自Thread类的:

public class HandlerThread extends Thread {

因此它完全可以当一个线程来使用,就像上面我们说的:new HandlerTread().start()这样,但是!!!——我们之前在Android中的消息机制——Looper、Handler、MessageQueue与Message这篇文章中说过,在子线程中创建一个Handler,需要手动创建Looper,即先Looper.parpare(),完了再Looper.loop()
这样,我们来看看HandlerThread中的run方法:

    @Override
    public void run() {
        mTid = Process.myTid();  //获得当前线程的id
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            //发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
            notifyAll();
        }
        //设置当前线程的优先级
        Process.setThreadPriority(mPriority);
        //该方法实现体是空的,子类可以实现该方法,作用就是在线程循环之前做一些准备工作,当然子类也可以不实现。
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

可以看到,这个run方法里边写死了,先Looper.prepare()然后Looper.loop(),也即是说,我们的HandlerThread这个特殊的“线程”是再带Looper的,因此——我们不能再主线程使用它,毕竟主线程是自带MainLooper的,一个线程只能有一个Looper,否则就会抛出异常。
  这里我们说一下,为什么要看这个run()方法,之前我们在Android中的线程形态(一)(进程/线程/线程池)中有讲过,Thread工作的时候,不论哪种实现方法,我们都必须重写这个类的run()方法,在其中写我们自己要做的事情。可以看到,HandlerThread类的run()方法已经给我们写死了,这就注定我们只能将它用在子线程中。

  我们再来看看HandelerThread类的额另一个重要的方法——getLooper():

    //该方法主要作用是获得当前HandlerThread线程中的mLooper对象
    public Looper getLooper() {
        if (!isAlive()) {   //如果线程不是活动的,则直接返回null
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        //如果线程已经启动,但是Looper还未创建的话,就等待,直到Looper创建成功
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    //这里会调用wait方法去等待,当run方法中的notifyAll方法调用之后
                    //通知当前线程的wait方法等待结束,跳出循环,获得mLooper对象的值。
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

OK,看到这里,结合上面的HandelrThread使用步骤,我们需要总结一下:

  • HandelrThread是一个自带Looper的线程,因此只能作为子线程使用
  • HandlerThread必须配合Handler使用,HandlerThread线程中做具体事情,必须要在Handler的callback接口中进行,他自己的run方法被写死了。
  • 子线程中的Handler与HandlerThread的联系是通过childHandler = new Handler(handlerThread.getLooper(), mSubCallback);这句来进行的,也就是你说,childHandler获得HandlerThread线程的Looper,这样,他们两个就在同一阵营了。这也就是在创建Handler作为HandlerThread线程消息执行者,必须在调用start()方法之后的原因——HandlerThread.start()之后,run()方法才能跑起来,Looper才能得以创建,handlerThread.getLooper()才不会出错。

二.IntentService的使用与实现原理

  HandlerThread在Android中的一个具体的应用就是IntentService。IntentService是继承于Service并处理异步请求的一个类。注意这句话——“继承于Service”“处理异步请求”“继承于Service”表示他是一个服务,我们知道Service是存在于主线程的,它之中不能存在耗时操作,否则的话回应起ANR,因此便有了IntentService——这个封装了HandlerThread和Handler的特殊Service。

1.IntentService的使用

第一步:创建一个继承IntentService类的子类
首先,通过源码我们知道,IntentService是一个继承自Service类的抽象类,因此我们必须创建一个继承它的子类才能实现相关功能:

public class MyIntentService extends IntentService {

    public ServiceUpdate() {
         // 注意构造函数参数为空,这个myIntentService字符串就是IntentService中工作线程的名字
         super("myIntentService");
    }

    //打印生命周期
    @Override
    public void onCreate() {
        Log.i("myIntentService", "onCreate");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("myIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
    
    @Override
      public void onStart(Intent intent, int startId) {
          Log.i("myIntentService", "onStart");
          super.onStart(intent, startId);
      }
    
     @Override
      public IBinder onBind(Intent intent) {
        Log.i("myIntentService", "onBind");
        return super.onBind(intent);
      }
      
    //重写onHandleIntent方法,在该方法中进行我们的各种事务处理
    @Override
    protected void onHandleIntent(Intent intent) {
         //一般Intent是从Activity发过来的,携带识别参数,根据参数不同执行不同的任务
        String taskName = intent.getExtras().getString("taskName");
        switch (taskName) {
        case "task1":
            Log.i("myIntentService", "task1");
            break;
        case "task2":
            Log.i("myIntentService", "task2");
            break;
        default:
            break;
        }
    }

    @Override
    public void onDestroy() {
        Log.i("myIntentService", "onDestroy");
        super.onDestroy();
    }
}

第二步:在Activity中开启服务:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        ......

        //同一服务只会开启一个工作线程,在onHandleIntent函数里依次处理intent请求。
        Intent intent1 = new Intent(this,MyIntentService.class);
        Bundle bundle = new Bundle();
        bundle.putString("taskName", "task1");
        intent1.putExtras(bundle);
        startService(intent1);

        Intent intent2 = new Intent(this,MyIntentService.class);
        Bundle bundle2 = new Bundle();
        bundle2.putString("taskName", "task2");
        intent2.putExtras(bundle2);
        startService(intent2);
    }
}

第三步:在 Manifest 中注册服务

<service android:name=".service.MyIntentService"/>

上述过程中我们启动了两个IntentService服务,打印出来生命周期为:onCreate -> onStartCommand -> onStart -> onStartCommand -> onStart -> task1 -> task2 -> onDestroy,OnBinder没有执行。
  从结果可以看到,onCreate 方法只执行了一次,而 onStartCommand 和 onStart 方法执行了两次,开启了两个 工作线程(Work Thread),这就证实了之前所说的,启动多次,但IntentService 的实例只有一个,这跟传统的Service 是一样的。

2.IntentServic源码分析

  IntentService继承Service类之后,整体的源码也就165行,跟HandlerThread差不多,我们先来看看它的实例变量:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;     //服务所在线程的Looper
    private volatile ServiceHandler mServiceHandler;    //构建一个服务中的Handler
    private String mName;       //服务所在线程的名字
    private boolean mRedelivery;
1).IntentService的构造方法

上面加了注释的三个变量较为重要,我们待会会一步步分析。接下来我们看看他的构造方法:

    public IntentService(String name) {
        super();
        mName = name;
    }

可以看到,上线例子中我们得

    public ServiceUpdate() {
         super("myIntentService");
    }

其中“myIntentService”实际会上传到父类IntentService的构造方法中,也就是IntentService所在线程的线程名字,IntentService构造方法中的super();又会进一步调用Service类中的构造函数:

    public Service() {
        super(null);
    }

再网上,就到ContextWrapper类里边去了。如果我们看过Activity的源码的话就会知道,Activity也是继承自ContextWrapper类的,而ContextWrapper类继承自Context!也就说,Service和Activity实际上都是一个Context!ContextWrapper有一个比较特殊的地方是,他的实例化的地方是在ActivityThread中由系统完成的,我们无权插手。因此,在StartService或者StartActivity的时候,系统就会自动帮我们实例化这两个组件。这样,Service的构造方法为空也就很好解释了。

2).OnCreat()

  看上面的生命周期流程图我们知道,IntentService第一步指定的onCreat方法:

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

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

首先,super.onCreate();,这个我们看到Service的onCreat()方法是空的,所以不管,接着看。HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");看到了吧,这里重量级人物——HandlerThread出场了,新建了一个HandlerThread实例,上面我们说过HandlerThread()的构造方法传进去的是HandlerThread所在线程的名字,然后thread.start();,嗯,非常符合我们上半片文章讲的HandlerThread使用流程。
  mServiceLooper = thread.getLooper();这里通过mServiceLooper获取到HandlerThread线程的Looper。然后mServiceHandler = new ServiceHandler(mServiceLooper);,我们接着看源码:

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

可以看到,ServiceHandler是IntentService类的一个内部类,并且继承自Handler,重写了handleMessage方法。很显然,这个类是用来处理startService(intent);传递来的消息的。这个消息处理的过程分为两步,第一调用onHandleIntent((Intent)msg.obj);方法;第二步stopSelf(msg.arg1);消息处理完后自动终止服务。所以我们接着而看onHandleIntent((Intent)msg.obj);:

    @WorkerThread
    protected abstract void onHandleIntent(Intent intent);

呦呵?这是一个抽象方法,抽象类中的抽象方法意味着我们需要在子类中重写这个方法,也就是说,这个方法才是我们处理消息的地方。可以看到,这个方法上面加了注解,是位于工作线程(子线程)中的。

3).onStartCommand()/onStart()

  好了,OnCreat()方法终于执行完了,onStartCommand()方法,它和onStart()方法实际上是连在一起的:

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

直接看onStart():

    @Override
    public void onStart(Intent intent, int startId) {
        //Service 启动后直接就向 mServiceHandler 发送消息,则马上就会执行 handleMessage方法
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

这个方法中,mServiceHandler这个Handler的子类,调用了obtainMessage()方法,该方法实际上上到Handler类中调用的是:

    public final Message obtainMessage(){
        return Message.obtain(this);
    }

这个方法中,Message.obtain(this);这个方法我们复习一下,实际上就是Handler从消息池中取出一个消息,避免Message类重复创建的一个方法。回到IntentSerice的onStart()方法中,Message msg = mServiceHandler.obtainMessage();,这句就是获取一个Message对象,然后mServiceHandler.sendMessage(msg);通过Handler发送消息,添加到Looper中的MessageQuenue队列中。

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

安全退出Looper()

3.IntentService中的一些坑

1).为什么不建议通过 bindService() 启动 IntentService?
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

  IntentService 源码中的 onBind() 默认返回 null;不适合 bindService() 启动服务,如果你执意要 bindService() 来启动 IntentService,可能因为你想通过 Binder 或 Messenger 使得 IntentService 和 Activity 可以通信,这样那么 onHandleIntent() 不会被回调,相当于在你使用 Service 而不是 IntentService。

2).为什么多次启动 IntentService 会顺序执行事件,停止服务后,后续的事件得不到执行?

  IntentService 中使用的 Handler、Looper、MessageQueue 机制把消息发送到线程中去执行的,所以多次启动 IntentService 不会重新创建新的服务和新的线程,只是把消息加入消息队列中等待执行,而如果服务停止,会清除消息队列中的消息,后续的事件得不到执行。

站在巨人的肩膀上摘苹果:
IntentService 示例与详解
Service 和 IntentService 的区别

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容