一、线程通讯问题

1.1 Message、Handler、Looper

在Android中提供了一种异步回调机制Handler,我们可以它来完成一个很长时间的任务。

Handler基本使用:

在主线程中,使用它很简单,new一个Handler对象实现其handleMessage方法,在handleMessage中,提供收到消息后相应的处理方法即可。

Message基本使用:

Message主要是进行消息的封装,并且同时可以指定消息的操作形式。

Looper基本使用:

当使用Handler处理Message的时候,实际上都是需要依靠一个Looper通道完成的,在一个Activity类中,会自动帮助程序员启动好Looper对象,而如果是一个用户自定义的类中,则需要用户手工使用Looper类中的若干方法之后才可以正常启动Looper对象。

1.2 基本用法: 定时更新文本内容:

public class MyMessageDemo extends Activity {
private static int count = 0; // 定义全局变量
public static final int SET = 1 ; // 设置一个what标记
private Handler myHandler = new Handler() { // 定义Handler对象
@Override
public void handleMessage(android.os.Message msg) {// 覆写此方法
switch (msg.what) { // 判断操作类型
case SET: // 为设置文本操作
MyMessageDemo.this.info.setText("Hello - " + count++);
}
}
};
private TextView info = null; // 文本显示组件
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.main);
this.info = (TextView) super.findViewById(R.id.info);
Timer timer = new Timer(); // 定义调度器
timer.schedule(new MyTask(), 0, 1000); // 立即开始,1秒一增长
}
private class MyTask extends TimerTask { // 定义定时调度的具体实现类
@Override
public void run() { // 启动线程
Message msg = new Message(); // 定义Message
msg.what = SET ; // 操作为设置显示文字
MyMessageDemo.this.myHandler.sendMessage(msg); // 发送消息到子线程
}
}
}

通过上述程序,我们已可以发现,UI界面中的数字在不停的自增。这是我们思考哇,为啥这么麻烦呢,非要在任务调度器中去发送消息,然后在消息中更新UI呢?我们直接在任务调度器中更新UI试下:

private class MyTask extends TimerTask {
@Override
public void run() {
MyMessageDemo.this.info.setText("MLDN - " + count++);
}
}

运行之后,系统报错:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

意思是,子线程无法更新主线程中各个组件的状态。 所以,我们必须在子线程返回要操作的消息,然后利用Handler处理消息。

1.3 通过上述程序可以发现,我们根本没有Looper对象,那么什么要Looper了,跟我们要用的Handler啥关系呢?

前面我们强调需要在主线程中使用Handler,为什么要这么说呢,因为你在自己new一个新线程中去像我前面那样简单建立一个Handler,程序执行是会报错的:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at com.cao.android.demos.handles.HandleTestActivity$MyThread$1.<init>(HandleTestActivity.java:86)
at com.cao.android.demos.handles.HandleTestActivity$MyThread.run(HandleTestActivity.java:86)

为什么在主线程中不会报错,而在自己新见的线程中就会报这个错误呢?很简单,因为主线程它已经建立了Looper,你可以打开ActivityThread的源码看一下:

public static final void main(String[] args) {
SamplingProfilerIntegration.start();
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop();
if (Process.supportsProcesses()) {
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.detach();
String name = (thread.mInitialApplication != null)
? thread.mInitialApplication.getPackageName()
: "<unknown>";
Slog.i(TAG, "Main thread of " + name + " is now exiting");
}

在main函数中它已经做了这个事情了,为什么要调用 Looper.prepareMainLooper(); Looper.loop();我们可以进去看一下,在prepareMainLooper方法中新建了一个looper对象,并与当前进程进行了绑定,而在Looper.loop方法中,线程建立消息循环机制,循环从MessageQueue获取Message对象,调用 
msg.target.dispatchMessage(msg);进行处理msg.target在myThreadHandler.sendEmptyMessage(0)设置进去的,因为一个Thead中可以建立多个Hander,通过msg.target保证MessageQueue中的每个msg交由发送message的handler进行处理,那么Handler又是怎样与Looper建立联系的呢,在Handler构造函数中有这样一段代码:

       mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;

在新建Handler时需要设置mLooper成员,Looper.myLooper是从当前线程中获取绑定的Looper对象:

public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}

若Looper对象没有创建,就会抛异常"Can't create handler inside thread that
has not called Looper.prepare()"

这跟我前面讲的是一致的。所以我们在一个新线程中要创建一个Handler就需要这样写:

 class MyThread extends Thread {
public void run() {
Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]-- run...", Thread
.currentThread().getName()));
// 其它线程中新建一个handler
Looper.prepare();// 创建该线程的Looper对象,用于接收消息,在非主线程中是没有looper的所以在创建handler前一定要使用prepare()创建一个Looper
myThreadHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--myThreadHandler handleMessage run...", Thread
.currentThread().getName()));
}
};
Looper.myLooper().loop();//建立一个消息循环,该线程不会退出
}
}

如何使用Looper呢,我们可以看一个复杂些的,主线程和子线程通讯的示例:

public class MyThreadDemo extends Activity
{
    public static final int SETMAIN = 1; // 设置一个what标记     public static final int SETCHILD = 2; // 设置一个what标记     private Handler mainHandler, childHandler; // 定义Handler对象     private TextView msg; // 文本显示组件     private Button but; // 按钮组件     class ChildThread implements Runnable
    { // 子线程类
        @Override
        public void run()
        {
            Looper.prepare(); // 初始化Looper
            MyThreadDemo.this.childHandler = new Handler()
            {
                public void handleMessage(Message msg)
                {
                    switch (msg.what)
                    { // 判断what操作
                        case SETCHILD: // 主线程发送给子线程的信息
                            System.out.println("*** Main Child Message : " + msg.obj); // 打印消息
                            Message toMain = MyThreadDemo.this.mainHandler.obtainMessage(); // 创建Message
                            toMain.obj = "\n\n[B] 这是子线程发给主线程的信息:" + super.getLooper().getThread().getName(); // 设置显示文字
                            toMain.what = SETMAIN; // 设置主线程操作的状态码
                            MyThreadDemo.this.mainHandler.sendMessage(toMain); // 发送消息
                            break;
                    }
                }
            };
            Looper.loop(); // 启动该线程的消息队列
        }
    }     @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.main); // 调用布局文件
        this.msg = (TextView) super.findViewById(R.id.msg); // 取得组件
        this.but = (Button) super.findViewById(R.id.but); // 取得按钮
        this.mainHandler = new Handler()
        { // 主线程的Handler对象
            public void handleMessage(Message msg)
            { // 消息处理
                switch (msg.what)
                { // 判断Message类型
                    case SETMAIN: // 设置主线程的操作类
                        MyThreadDemo.this.msg.setText("主线程接收数据:" + msg.obj.toString()); // 设置文本内容
                        break;
                }
            }
        };
        new Thread(new ChildThread(), "Child Thread").start(); // 启动子线程
        this.but.setOnClickListener(new OnClickListenerImpl()); // 单击事件操作
    }     private class OnClickListenerImpl implements OnClickListener
    {
        @Override
        public void onClick(View view)
        {
            if (MyThreadDemo.this.childHandler != null)
            { // 已实例化子线程Handler
                Message childMsg = MyThreadDemo.this.childHandler.obtainMessage(); // 创建一个消息
                childMsg.obj = MyThreadDemo.this.mainHandler.getLooper().getThread().getName() + " --> Hello MLDN ."; // 设置消息内容
                childMsg.what = SETCHILD; // 操作码
                MyThreadDemo.this.childHandler.sendMessage(childMsg); // 向子线程发送
            }
        }
    }     @Override
    protected void onDestroy()
    {
        super.onDestroy();
        MyThreadDemo.this.childHandler.getLooper().quit(); // 结束队列
    }
}

我们可以发现,在ChildThread子线程中,我们必须借助Looper,才可以完成与主线程的通讯。

Handler用法总结的更多相关文章

  1. Handler用法

    1.子线程创建handler 方法一 HandlerThread handlerThread = new HandlerThread(" sub thread name");  / ...

  2. Android之Handler用法总结(1)

    方法一:(java习惯,在android平台开发时这样是不行的,因为它违背了单线程模型) 刚刚开始接触android线程编程的时候,习惯好像java一样,试图用下面的代码解决问题 new Thread ...

  3. Android之Handler用法总结

    方法一:(java习惯,在android平台开发时这样是不行的,因为它违背了单线程模型) 刚刚开始接触android线程编程的时候,习惯好像java一样,试图用下面的代码解决问题 new Thread ...

  4. android中handler用法总结

    一.Handler的定义: Handler主要接收子线程发送的数据, 并用此数据配合主线程更新UI,用来跟UI主线程交互用.比如可以用handler发送一个message,然后在handler的线程中 ...

  5. Android(java)学习笔记134:Handler用法总结 和 秒表案例

    一.Handler的定义: Handler主要接收子线程发送的数据, 并用此数据配合主线程更新UI,用来跟UI主线程交互用.比如可以用handler发送一个message,然后在handler的线程中 ...

  6. Android学习笔记--Handler用法总结

    不错的例子:http://www.cnblogs.com/menlsh/archive/2013/06/07/3125341.html 转自:一叶知秋的博客 http://blog.sina.com. ...

  7. 异步消息处理机制——Handler用法

    Handler 1. Message Messsge是线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据,Message的what字段,除此之外还可以使用arg1和arg2字段 ...

  8. andorid 多线程handler用法

    .xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ...

  9. Android之Handler用法总结/安卓中只有主线程可以修改UI

    Handler传递消息的方式可以实现实时刷新以及长按连续响应事件. 按钮响应 btnadd_fcl.setOnTouchListener(new View.OnTouchListener() { pr ...

随机推荐

  1. 百度DMLC分布式深度机器学习开源项目(简称“深盟”)上线了如xgboost(速度快效果好的Boosting模型)、CXXNET(极致的C++深度学习库)、Minerva(高效灵活的并行深度学习引擎)以及Parameter Server(一小时训练600T数据)等产品,在语音识别、OCR识别、人脸识别以及计算效率提升上发布了多个成熟产品。

    百度为何开源深度机器学习平台?   有一系列领先优势的百度却选择开源其深度机器学习平台,为何交底自己的核心技术?深思之下,却是在面对业界无奈时的远见之举.   5月20日,百度在github上开源了其 ...

  2. python 匹配集合与补集

  3. c++中的c_str()函数

    //c_str()是为了与C语言兼容,把c++中string类型的字符串返回首地址使用 #include <iostream>#include <string.h>using ...

  4. laravel 图片

    /** * 缩略图上传 */ public static function addPic() { $inputData = request()->all(); $rules = [ 'main_ ...

  5. Timer和DispatcherTimer简单使用

    1. 设定计时器相关属性,使用委托方法处理事件触发 DispatcherTimer dispatcherTimer= new DispatcherTimer(); dispatcherTimer.Ti ...

  6. 【To Read】Shortest Palindrome(KMP)

    题意:Given a string S, you are allowed to convert it to a palindrome by adding characters in front of ...

  7. Servlet过虑器

    过滤器是在请求的预处理和后处理时调用的对象. 主要用于执行转换,日志记录,压缩,加解密,输入验证等过滤任务. servlet过滤器是可插拔的,即它在web.xml文件中定义,如果从web.xml文件中 ...

  8. json,pickle模块

    序列化 把对象从内存中编成可储存或传输的过程称之为序列化,输出为json串,.json文件 反序列化 把json串反编成Python数据类型 json模块 用于跨平台交互 json模块下不可转换集合( ...

  9. Python学习之路2☞数据类型与变量

    变量 变量作用:保存状态:说白了,程序运行的状态就是状态的变化,变量是用来保存状态的,变量值的不断变化就产生了运行程序的最终输出结果 一:声明变量 #!/usr/bin/env python # -* ...

  10. UVA_10071:Back to High School Physics

    Language:C++ 4.8.2 #include<stdio.h> int main(void) { int v, t; while(scanf("%d%d", ...