14

Android HandlerThread 完全解析

 3 years ago
source link: https://blog.csdn.net/lmj623565791/article/details/47079737
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Android HandlerThread 完全解析

转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/47079737
本文出自:【张鸿洋的博客】

话说最近股市变动不变,也成了热火朝天的话题。不知道大家有没有考虑做个实时更新股市数据的app呢?假设我们要做一个股市数据实时更新的app,我们可以在网上找个第三方的股市数据接口,然后在我们的app中每隔1分钟(合适的时间)去更新数据,然后更新我们的UI即可。

当然了,本文不是要教大家做这样一个app,只是举个场景。言归正传,回到我们的HandlerThread,大家一定听说过Looper、Handler、Message三者的关系(如果不了解,可以查看Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系),在我们的UI线程默默的为我们服务。其实我们可以借鉴UI线程Looper的思想,搞个子线程,也通过Handler、Message通信,可以适用于很多场景。

对了,我之前写过一篇博文Android Handler 异步消息处理机制的妙用 创建强大的图片加载类,这篇博文中就在子线程中创建了Looper,Handler,原理和HandlerThread一模一样,可惜当时我并不知道这个类,不过大家也可以当做HandlerThread应用场景进行学习。

2、HandlerThread实例

下面看我们模拟大盘走势的代码,其实非常简单,就一个Activity


package com.zhy.blogcodes.intentservice;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.widget.TextView;

import com.zhy.blogcodes.R;


public class HandlerThreadActivity extends AppCompatActivity
{

    private TextView mTvServiceInfo;

    private HandlerThread mCheckMsgThread;
    private Handler mCheckMsgHandler;
    private boolean isUpdateInfo;

    private static final int MSG_UPDATE_INFO = 0x110;

    //与UI线程管理的handler
    private Handler mHandler = new Handler();


    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread_handler);

        //创建后台线程
        initBackThread();

        mTvServiceInfo = (TextView) findViewById(R.id.id_textview);

    }

    @Override
    protected void onResume()
    {
        super.onResume();
        //开始查询
        isUpdateInfo = true;
        mCheckMsgHandler.sendEmptyMessage(MSG_UPDATE_INFO);
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        //停止查询
        isUpdateInfo = false;
        mCheckMsgHandler.removeMessages(MSG_UPDATE_INFO);

    }

    private void initBackThread()
    {
        mCheckMsgThread = new HandlerThread("check-message-coming");
        mCheckMsgThread.start();
        mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())
        {
            @Override
            public void handleMessage(Message msg)
            {
                checkForUpdate();
                if (isUpdateInfo)
                {
                    mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000);
                }
            }
        };


    }

    /**
     * 模拟从服务器解析数据
     */
    private void checkForUpdate()
    {
        try
        {
            //模拟耗时
            Thread.sleep(1000);
            mHandler.post(new Runnable()
            {
                @Override
                public void run()
                {
                    String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 3000 + 1000));
                    mTvServiceInfo.setText(Html.fromHtml(result));
                }
            });

        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }

    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        //释放资源
        mCheckMsgThread.quit();
    }


}

可以看到我们在onCreate中,去创建和启动了HandlerThread,并且关联了一个mCheckMsgHandler。然后我们分别在onResume和onPause中去开启和暂停我们的查询,最后在onDestory中去释放资源。

这样就实现了我们每隔5s去服务端查询最新的数据,然后更新我们的UI,当然我们这里通过Thread.sleep()模拟耗时,返回了一个随机数,大家可以很轻易的换成真正的数据接口。

布局文库

<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin">

    <TextView
        android:id="@+id/id_textview"
        android:text="正在加载大盘指数..."
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

运行效果图

20150727085713592

别问我为什么要用红色!!!

ok,当然了,我们的效果很单调,但是你完全可以去扩展,比如ListView显示用户关注的股票数据。或者是其他的需要一直检测更新的数据。

看到这,你是否好奇呢,HandlerThread的内部是如何做的呢?其实非常的简单,如果你看过Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系估计扫几眼就会了。

HandlerThread 源码分析

对于所有Looper,Handler相关细节统一参考上面提到的文章。

我们轻轻的掀开HandlerThread的源码,还记得我们是通过

 mCheckMsgThread = new HandlerThread("check-message-coming");
 mCheckMsgThread.start(); 

创建和启动的对象,那么随便扫一眼:


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

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

看到了什么,其实我们就是初始化和启动了一个线程;然后我们看run()方法,可以看到该方法中调用了Looper.prepare(),Loop.loop();

prepare()呢,中创建了一个Looper对象,并且把该对象放到了该线程范围内的变量中(sThreadLocal),在Looper对象的构造过程中,初始化了一个MessageQueue,作为该Looper对象成员变量。

loop()就开启了,不断的循环从MessageQueue中取消息处理了,当没有消息的时候会阻塞,有消息的到来的时候会唤醒。如果你不明白我说的,参考上面推荐的文章。


接下来,我们创建了一个mCheckMsgHandler,是这么创建的:

mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())
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;
    }

mCheckMsgThread.getLooper()返回的就是我们在run方法中创建的mLooper。

那么Handler的构造呢,其实就是在Handler中持有一个指向该Looper.mQueue对象,当handler调用sendMessage方法时,其实就是往该mQueue中去插入一个message,然后Looper.loop()就会取出执行。

好了,到这我们就分析完了,其实就几行代码;不过有一点我想提一下:

如果你够细心你会发现,run方法里面当mLooper创建完成后有个notifyAll(),getLooper()中有个wait(),这是为什么呢?因为的mLooper在一个线程中执行,而我们的handler是在UI线程初始化的,也就是说,我们必须等到mLooper创建完成,才能正确的返回getLooper();wait(),notify()就是为了解决这两个线程的同步问题。

不过对于这样的线程间的同步问题,我非常喜欢使用Semaphore。

还记得在Android Handler 异步消息处理机制的妙用 创建强大的图片加载类一文中有类似的说明:

如果你比较细心,可能会发现里面还有一些信号量的操作的代码,如果你不了解什么是信号量,可以参考:Java 并发专题 : Semaphore 实现 互斥 与 连接池 。 简单说一下mSemaphore(信号数为1)的作用,由于mPoolThreadHander实在子线程初始化的,所以我在初始化前调用了mSemaphore.acquire去请求一个信号量,然后在初始化完成后释放了此信号量,我为什么这么做呢?因为在主线程可能会立即使用到mPoolThreadHander,但是mPoolThreadHander是在子线程初始化的,虽然速度很快,但是我也不能百分百的保证,主线程使用时已经初始化结束。

哈,当时也有很多人问,为什么使用这个Semaphore,到这里我想大家应该清楚了。话说假设我当时真的HanderThread这个类,可能之前的代码能简化不少呢~

对了,你可能会问与Timer相比有什么优势呢?

直接参考这篇文章吧:Handler vs Timer

欢迎关注我的微博http://weibo.com/u/3165018720

群号:463081660,欢迎入群

微信公众号:hongyangAndroid
(欢迎关注,第一时间推送博文信息)
1422600516_2905.jpg


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK