在Android编程的过程中,如果在Activity中某个操作会运行比较长的时间,比如:下载文件。这个时候如果在主线程中直接下载文件,会造成Activity卡死的现象;而且如果时间超过5秒,会有ANR报错。

在这种情况下, 可以使用Thread来处理,而如果在这期间需要根据Thread中的操作来更新界面,就需要使用Handler来处理。

涉及到的类主要有:Handler、Thread、Message、MessageQueue、Looper、HandlerThread

  如果是针对上面的情况,可以只使用Handler、Message和Thread就可以解决。在Thread中处理下载文件的过程,并在结束后,向Handler发送消息(Message)。Handler在接收到消息后对界面进行修改。

  如果只是不想在界面线程中进行下载文件的操作,那只需要创建一个新的线程来处理下载文件的过程就可以,为什么还要使用Handler呢?Handler应用在需要更新界面的场景中,因为Android的界面线程是不安全的,所以如果直接在Thread中修改界面会报错,所以要使用Handler来处理。当在界面线程中获取Handler时,Handler将和界面线程在同一个线程中,所以通过Handler修改界面将不会报错。

一、实现上面的需求

1、Handler要重写handleMessage方法,用来处理修改界面的逻辑。

        handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg); switch (msg.what) {
case MessageType.ProgressType :
int arg1 = msg.arg1;
if (arg1 >= maxProgress) {
Toast.makeText(MainActivity.this, "Progress is OK", Toast.LENGTH_SHORT);
return;
} else if (arg1 < 0) {
arg1 = 0;
}
progressBar.setProgress(arg1);
break; default : break;
}
}
};

说明:

  • 此处直接使用没有参数的Handler构造函数,表示获取当前线程的Looper,而创建主线程的时候默认生成了一个Looper,所以在主线程的Activity中,可以直接使用Handler的无参构造函数。
  • 如果在主线程中想要获取主线程的Looper,可以使用Looper.myLooper()方法获取。
  • 在其他线程中要想获取主线程的Looper,需要使用Looper.getMainLooper()方法获取,在主线程中也可以使用这个方法获取。
  • 在其他线程中,如果直接使用Looper.myLooper()方法,将获取到null。

2、Thread中要在下载完文件后(我使用了TimerTask和Timer来实现100毫秒进度加1),向Handler发送消息。

public class MyProgressTimerTask extends TimerTask {

    private Handler handler;
private int count; public MyProgressTimerTask(Handler handler) {
this.handler = handler;
count = 0;
} @Override
public void run() {
// TODO Auto-generated method stub
count++;
Message message = handler.obtainMessage();
message.what = MessageType.ProgressType;
message.arg1 = count;
handler.sendMessage(message); Looper looper = Looper.myLooper();
if (null == looper) {
System.out.println(">>>>>>>>>>>>>>>>Looper.myLooper is null<<<<<<<<<<<<<<");
}
} }

3、在点击开始按钮时,启动Timer;点击停止时,关闭Timer。也可以不使用Timer,而是使用Thread,在Thread中sleep一段时间,依然可以达到预期的效果。同时,两种方式都可以对Activity进行操作,不会有死机的感觉。

     progressBar = (ProgressBar) findViewById(R.id.progressBar);
maxProgress = progressBar.getMax(); btnStartProgress = (Button) findViewById(R.id.btnStartProgress);
btnStartProgress.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
task = new MyProgressTimerTask(handler);
timer = new Timer();
timer.schedule(task, 1000, 100);
}
}); btnStopProgress = (Button) findViewById(R.id.btnStopProgres);
btnStopProgress.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (task != null) {
task.cancel();
}
if (timer != null) {
timer.cancel();
}
task = null;
timer = null;
}
});

注意:在生成Message的时候推荐使用Handler.obtainMessage()和Message.obtain()方法(其实最终都是调用Message.obtain()方法),因为这两个方法不是简单的new一个对象。分析代码可以看出,它会先判断消息池中是否有可用的消息(由sPool指示开始Message的Message链表),如果没有才会去创建新的Message。这样可以减少频繁创建销毁消息的消耗。

二、不在主线程中创建Handler对象(使用Handler和Looper)

  如果创建Handler的操作不在主线程中,那么在构造Handler对象的时候,就需要传递一个Looper对象。因为如果不传递Looper对象,Handler将调用Looper.myLooper()方法,然后判断是否获取到了Looper对象,如果没有,将抛出异常。而只有主线程在创建的时候才会自动创建Looper对象,所以在非主线程中,调用这样的Handler构造函数,一定会抛出异常。

  当然,上面为了说明的方便,把事情说的太死。其实在非主线程中构造Handler对象的时候,还是可以不传递Looper对象,但是在调用Handler构造函数之前,一定要调用Looper.prepare方法。因为Looper没有public的构造函数,只能通过Looper.prepare来创建Looper对象。prepare方法在创建了Looper对象之后,会将这个Looper对象与当前线程相关联,所以之后Hanlder中再调用Looper.myLooper()方法就可以获取到Looper对象了。prepare方法也不能多次调用,如果线程已经关联了Looper对象,再调用prepare方法,将抛出异常。

  然后进行一些初始化信息,比如构造Handler对象。

  最后调用Looper.loop方法。需要注意的是这个方法将循环读取MessageQueue中的信息,并处理这些信息。这个方法下面的代码暂时将不会执行,直到调用Looper对象的quit方法,所以所有的初始化代码一定要放在Looper.loop()之前。

三、用HandlerThread创建线程

  上面说明的是在非主线程中,如果要使用Handler的话,需要使用上面的说法。其实Android提供了更加简洁方便的方式——使用HandlerThread来创建新的线程。

  如果已经知道在新线程中需要使用Handler对象,则可以用HandlerThread来创建新线程。可以调用HandlerThread对象的getLooper方法获取这个线程关联的Looper对象。然后将这个Looper对象传递给Handler对象。

  因为HandlerThread重写了Thread的run方法,在run方法中,调用了Looper.prepare、Looper.loop方法,所以不需要自己来实现这部分代码。

  HandlerThread提供了一个方法onLooperPrepared回调方法,可以重写这个方法,进行一些初始化的工作,比如构造Handler对象(此时,可以不传递Looper对象)。这个方法处在Looper.prepare()和Looper.loop()方法之间。

主线程:

  Activity的绘制是在主线程中完成的,而主线程在创建的时候,就会创建Looper对象,所以界面线程使用Handler的时候,不需要考虑创建Looper的问题。那是不是Activity等组件的所有操作都在主线程中进行呢?通过实验发现,基本可以这样理解,即使这个组件是由new Thread调用startActivity创建的。

  我写了这样一段代码。首先在启动的Activity中,启动一个新的线程,在这个新的线程中分别启动一个Activity、Service、BroadcastReceiver。在Activity的onCreate、onStart、onResume、onPause、onStop、onDestroy、onRestart、onActivityResult方法中打印当前线程,结果打印出的都是主线程。在Service的onCreate中和BroadcastReceiver的onReceive中也是相同的效果。所以基本可以说组件的操作都是在主线程中进行的。

  因此,如果Handler主要用途是修改界面的话,那HandlerThread就不需要使用了。但是如果考虑到这样的场景,在新启动的线程A中,任务还是太多,又必须启动其他线程,而线程A又需要获得其他线程的返回结果。这时,使用HandlerThread可能就能很方便的解决问题了。Looper的使用应该很少了,HandlerThread的作用就是屏蔽掉Looper,直接使用Looper比较复杂,需要先使用Looper.prepare,然后在所有操作结束后调用Looper.loop使其循环读取消息队列中的消息。使用HandlerThread后,程序将极度简化。

  

参考资料 :

深入理解Android消息处理系统——Looper、Handler、Thread

深入剖析Android消息机制

Android HandlerThread 的使用及其Demo

Android Handler 异步调用修改界面与主线程的更多相关文章

  1. tornado 异步调用系统命令和非阻塞线程池

    项目中异步调用 ping 和 nmap 实现对目标 ip 和所在网关的探测 Subprocess.STREAM 不用担心进程返回数据过大造成的死锁, Subprocess.PIPE 会有这个问题. i ...

  2. handler.postDelayed(new Runnable()){ }运行在主线程吗

    答案:是的. handler.postDelayed(new Runnable() { @Override public void run() { tv_word.setVisibility(View ...

  3. Android Handler 异步消息处理机制的妙用 创建强大的图片加载类(转)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 最近创建了一个群,方便大家交流,群号: ...

  4. Android Handler 异步消息处理机制的妙用 创建强大的图片载入类

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 近期创建了一个群.方便大家交流,群号: ...

  5. Android之异步调用

    概述 AsyncTask可以很好的,准确的使用UI线程,他可以将一个比较耗时(几秒钟)的动作运行在后台,并且能将结果返回至UI线程中,不需要通过(Thread操作和Handler操作). 使用时必须通 ...

  6. Android ------ handler 异步处理消息

    Handler基本概念: Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分逐个的在消息队列中将消息取出,然后对消息进行出来,就是发 ...

  7. Android 之异步任务(AsyncTask,Handler,Message,looper)

    AsyncTask: 3个类型(Params,Progress和Result),4个步骤(onPreExecute(),doInBackground(Params…),onProgressUpdate ...

  8. android 调用系统界面

    现在开发中的功能需要直接跳转到拨号.联系人.短信界面等等,查找了很多资料,自己整理了一下. 首先,我们先看拨号界面,代码如下: Intent intent =new Intent(); intent. ...

  9. Android Handler学习笔记

    已经习惯了挖坑不填,继续任性一下,周一到周五继续挖坑,每周六周日负责填坑. 1.从Android UI线程谈起 出于性能考虑,Android 中的UI操作并不是线程安全的,所以Android中规定只能 ...

随机推荐

  1. 【转】Python 可视化神器-Plotly Express

    转自:https://mp.weixin.qq.com/s/FNpNJSMK5Vs8pwi0PbbBzw 说明:图片无法直接复制,请查看原文 导读:Plotly Express 是一个新的高级 Pyt ...

  2. Python2.7-getpass

    getpass模块,当用户输入密码时,可以不在屏幕上显示,但是用户也不能看到自己输了几位输了什么 1.模块方法 1.1 getpass.getpass([prompt[, stream]]):prom ...

  3. (转)JVM调优常用命令(jstat、jmap、jstack)

    原文:https://www.cnblogs.com/ityouknow/p/5714703.html 一.jstat jstat(JVM statistics Monitoring)是用于监视虚拟机 ...

  4. Xcode 备忘

    一. 打印一堆乱七八糟的东西: Edit Scheme... --> Run --> Arguments,在 Environment Variables 里添加 OS_ACTIVITY_M ...

  5. MVC bundle的使用总结

    在我们的项目里面充斥着很多静态文件,为了追求模块化.插件化很多静态文件都被设计成模块的方式或者被分解,在需要的时候在通过组合的方式在UI层上使用:这就带来一个问题,文件多了会影响浏览器加载页面的速度, ...

  6. jqgrid 单列排序和组合排序

    有时,我们需要设置jqgrid表格按某个列排序,或则按多个列组合排序.如何实现? 1)设置可以排序的列  sortable: true 2)设置 multiSort: true 启用组合排序 $(&q ...

  7. [POI2007]旅游景点atr BZOJ1097

    分析: 我们可以考虑,因为我们必须经过这些节点,那么我们可以将它状压,并且我们因为可以重复走,只是要求停顿前后,不要求遍历前后,那么我们之间存一下点与点之间的最短路,之后每次转移一下就可以了. f[i ...

  8. Elasticsearch 简介

    1. 背景 Elasticsearch 在公司的使用越来越广,很多同事之前并没有接触过 Elasticsearch,所以,最近在公司准备了一次关于 Elasticsearch 的分享,整理成此文.此文 ...

  9. PI monitor error process-RESOURCE_NOT_FOUND-转

    事务:sxi_monitor 状态:system error 类型:Request Message Mapping 错误简要:RESOURCE_NOT_FOUND 错误详细信息: <?xml v ...

  10. Java中枚举的写法和用法

            在公司代码中,用了一大堆的枚举,看得我好懵逼.下面开始看看枚举怎么写和怎么用. 一.枚举的写法         关于枚举的写法,网上好多这方面的知识.这里直接贴一个我自己写的枚举类的代 ...