• Handler用法总结


    一、线程通讯问题

    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 = "
    
    [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,才可以完成与主线程的通讯。

  • 相关阅读:
    网站的域名带www的和不带www的有什么区别呀
    网站域名加WWW与不加WWW区别
    QQ第三方登录报错error=-1
    Centos7 Apache配置虚拟主机的三种方式
    HTTP和HTTPS有什么区别? 什么是SSL证书?使用ssl证书优势?
    微博第三方登录时,域名使用错误报错, Laravel Socialite Two InvalidStateException No message
    Laravel5.1 实现第三方登录认证教程之
    php第三方登录(微博登录,仿照慕课网)
    php实现第三方登录
    laravel5.4 前后台未登陆,跳转到各自的页面
  • 原文地址:https://www.cnblogs.com/hehe520/p/6330027.html
Copyright © 2020-2023  润新知