欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android多线程-HandlerThread的简单使用与剖析

程序员文章站 2022-06-10 10:38:19
...

前面我们讲到,Android中子线程与主线程通信我们主要用到Handler,在一些轻量级的需求上,可以使用AsyncTask。但是使用Handler有没有简单的方法实现主线程与子线程的通信呢。Android已经为我们内置了这么一个类HandlerThread,顾名思义,这是一个线程类,可以让我们更方便地实现线程间通信,那么,下面我们就通过一个简单的例子来了解一下这个类的使用方法。

简单使用

这里我们使用一个按钮来模拟手机收到服务器推送消息,收到推送消息后我们需要进行下载操作,这当然放在子线程中进行,子线程将下载进度传给主线程并且在下载完成后通知主线程进行安装操作。简单的逻辑,我们先来看看本章的主角HandlerThread的用法:

private void initThread() { 
    //初始化工作线程
    workerThread = new HandlerThread("down-load-thread");
    //启动工作线程
    workerThread.start();
    //初始化工作线程handler,传入Looper实现与工作线程的绑定
    workerHandler = new DownLoadHandler(workerThread.getLooper(),
                                        new WeakReference<HandlerThreadDemoActivity>(this));
    //初始化主线程
    mainHandler = new MainHandler(new WeakReference<HandlerThreadDemoActivity>(this));
}

可以看到使用方法还是很简单的,我们需要先对HandlerThread进行初始化,并且启动这个线程,然后将该线程的Looper传给工作线程的消息处理器workerHandler,接下来我们初始化一个主线程的消息处理器mainHandler。初始化工作就完成了,接下来是点击按钮的操作:

/**
 * Button点击事件,模拟收到推送消息,开始下载
 * @param view
 */
protected void getPushMessage(View view) {
    //模拟收到推送消息,请求下载
    Message message = Message.obtain();
    message.what = DOWN_LOAD_TASK;
    message.obj = downloadUrl;
    //工作线程消息处理器发出消息,消息会进入工作线程的MessageQueue中,
    // 并被工作线程Looper取出并重新分发给workerHandler
    workerHandler.sendMessage(message);
}

点击事件很简单,向工作线程消息处理器workerHandler发送一个包含下载链接的消息,消息会进入工作线程的MessageQueue中,并最终被工作线程Looper取出并重新分发给workerHandler,我们来看看这个工作线程消息处理器DownLoadHandler这个类:

//静态内部类加弱引用,防止内存泄漏
static class DownLoadHandler extends Handler {
    //这里使用弱引用,防止内存泄漏
    private WeakReference<HandlerThreadDemoActivity> activityWeakReference;
    public DownLoadHandler(Looper looper,WeakReference<HandlerThreadDemoActivity> activityWeakReference) {
        super(looper);
        this.activityWeakReference = activityWeakReference;
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.e("THREAD", "DownLoadHandler handleMessage"+Thread.currentThread().getId()+"");
        if(msg.what == DOWN_LOAD_TASK) {
            //判断收到消息为要求下载
            String url = (String) msg.obj;
            if( !TextUtils.isEmpty(url)) {
                download(url);
            }
        }
    }

    private void download(String url) {
        try{
            //下载路径
            String path = Environment.getExternalStorageDirectory().getAbsolutePath();

            //文件名
            String filename=url.substring(url.lastIndexOf("/") + 1);
            //获取文件名
            URL myURL = new URL(url);
            URLConnection conn = myURL.openConnection();
            conn.connect();
            InputStream is = conn.getInputStream();
            int fileSize = conn.getContentLength();//根据响应获取文件大小
            if (fileSize <= 0) throw new RuntimeException("无法获知文件大小 ");
            if (is == null) throw new RuntimeException("stream is null");
            File file = new File(path+"/"+filename);
            if(!file.exists()){
                file.mkdirs();
            }
            //把数据存入路径+文件名
            FileOutputStream fos = new FileOutputStream(file);
            byte buf[] = new byte[1024];
            int downLoadFileSize = 0;//记录已下载大小
            int numRead = is.read(buf);
            while(numRead != -1 && activityWeakReference.get().isRunning) {
                fos.write(buf, 0, numRead);
                downLoadFileSize += numRead;
                numRead = is.read(buf);
                //更新进度条
                float percent = downLoadFileSize / (fileSize * 1f);
                Message message = Message.obtain();
                message.what = UPDATE_PROGRESS;
                if(percent == 1) {
                    Bundle bundle = new Bundle();
                    bundle.putString("file", file.getAbsolutePath());
                    message.setData(bundle);
                }
                message.obj = percent;
                activityWeakReference.get().sendProgressMessage(message);
            }
            is.close();
            fos.close();
        } catch (Exception ex) {
            Log.e("DOWNLOAD", "error: " + ex.getMessage(), ex);
        }
    }
}

/**
     * 发送更新进度条消息,在子线程中执行
     * @param message 消息体
     */
public void sendProgressMessage(Message message) {
    Log.e("THREAD", "sendProgressMessage"+Thread.currentThread().getId()+"");
    mainHandler.sendMessage(message);//子线程将消息发给主线程
}

工作线程的消息处理逻辑很简单,收到消息判断如果是要求下载,开始下载,并在循环中将下载进度发送给主线程的消息处理器mainHandler,这里要注意sendProgressMessage方法是运行于子线程中的,感兴趣的可以看下日志中打印的线程id。接下来消息已经发到了mainHandler,MainHandler类中的处理逻辑是这样的:

//使用静态内部类防止内存泄漏
static class MainHandler extends Handler {
    private WeakReference<HandlerThreadDemoActivity> activityWeakReference;

    public MainHandler(WeakReference<HandlerThreadDemoActivity> activityWeakReference) {
        this.activityWeakReference = activityWeakReference;
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.e("THREAD", "MainHandler handleMessage"+Thread.currentThread().getId()+"");
        if(msg.what == UPDATE_PROGRESS) {
            //获取到更新进度条的消息
            float percent = (float) msg.obj;
            if(percent != 1) {
                activityWeakReference.get().updateProgress(percent);
            } else {
                Bundle bundle = msg.getData();
                String filePath = bundle.getString("file");
                activityWeakReference.get().updateProgress(percent,filePath);
            }
        }
    }
}

/**
 * 更新UI操作,主线程进行
 * @param percent
 */
public void updateProgress(float percent) {
    updateProgress(percent,null);
}

public void updateProgress(float percent,String filePath) {
    Log.e("THREAD", "updateProgress"+Thread.currentThread().getId()+"");
    tvProgressValue.setText("当前进度:"+String.format("%.2f%%",percent * 100));
    pbLoading.setProgress((int) (percent * PROGRESS_MAX_VALUE));
    if(percent == 1) {
        Toast.makeText(this,"下载完成",Toast.LENGTH_SHORT).show();
        install(filePath);
    }
}

收到消息,更新UI,判断下载完成进行安装,很简单的逻辑。

最后别忘了在Activity销毁时关闭HandlerThread:

@Override
protected void onDestroy() {
    isRunning = false;//设置运行状态为false
    if(workerThread != null) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            workerThread.quitSafely();//线程安全
        } else {
            workerThread.quit();//非线程安全
        }
    }
    if(workerHandler != null) {
        workerHandler.removeCallbacksAndMessages(null);
    }
    if(mainHandler != null) {
        mainHandler.removeCallbacksAndMessages(null);
    }
    super.onDestroy();
}

至此,全部代码完成。

效果如图:

Android多线程-HandlerThread的简单使用与剖析

原理浅析

接下来我们简单聊聊HandlerThread的原理,我们之前在讲到Handler的时候有说过,Handler不能直接在子线程中使用,在子线程中使用前需要先进行相关操作,那么我们查看HandlerThread的源码看看HandlerThread的相关方法中是否初始化了Looper对象:

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

可以看到其构造方法中很简单的只是设置了该线程的优先级而已,继续往下看:

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

可以看到在线程调用start方法后会紧接着执行run方法,其中我们看到了猜测的Looper.prepare,通过之前的学习我们知道这个方法用于生成一个Looper,并且初始化一个MessageQueue。紧接着,使用同步锁将生成的looper对象与当前线程绑定,而后调用notifyAll方法通知其他线程Looper初始化完毕(主要是为了通知getLooper中的wait方法),保证线程同步。接下来是一个回调方法,然后是Looper.loop开始轮询,这就与我们之前讲到的子线程中使用Handler的知识一样了。

好了,这篇文章就是这样,如果您对文章内容有疑惑或者需要纠错,请评论联系我,本文代码地址在github,欢迎查看,提意见.

也欢迎各位访问我的个人博客,一起交流、学习~

See you~