Handler机制从入门到放弃(一)

作者: 李牧羊 | 来源:发表于2016-10-30 19:03 被阅读346次

    闲来无事,准备好好梳理一下Handler机制,之前分析过没有写成博客,结果就是慢慢的淡忘了,这次趁着刚分析完,赶紧写下来。

    在开始分析之前先打打基础,理解理解什么是线程以及什么是Handler,这里大部分内容引用一篇来自伯乐在线的文章,因为看来看去关于基础的部分这个人已经说得很好了,我就负责把主要的部分抽取出来。

    原文地址:Android线程和Handler基础入门


    现在大多数的移动设备已经变得越来越快,但是它们其实也不算是非常快。如果你想让你的APP既可以承受一些繁杂的工作而又不影响用户体验的话,那么必须把任务并行执行。在Android上,我们使用线程。

    什么是线程?

    线程或者线程执行本质上就是一串命令(也是程序代码),然后我们把它发送给操作系统执行。

    线程

    一般来说,我们的CPU在任何时候一个核只能处理一个线程。多核处理器(目前大多数Android设备已经都是多核)顾名思义,就是可以同时处理多线程(通俗地讲就是可以同时处理多件事)。

    多核处理与单核多任务处理的实质

    上面我说的是一般情况,并不是所有的描述都是一定正确的。因为单核也可以用多任务模拟出多线程。

    每个运行在线程中的任务都可以分解成多条指令,而且这些指令不用同时执行。所以,单核设备可以首先切换到线程1去执行指令1A,然后切换到线程2去执行指令2A,接着返回到线程1再去执行1B、1C、1D,然后继续切换到线程2,执行2B、2C等等,以此类推。

    这个线程之间的切换十分迅速,以至于在单核的设备中也会发生。几乎所有的线程都在相同的时间内进行任务处理。其实,这都是因为速度太快造成的假象,就像电影《黑客帝国》里的特工Brown一样,可以变幻出很多的头和手。

    Java核心里的线程

    在Java中,如果要想做平行任务处理的话,会在Runnable里面执行你的代码。可以继承Thread类,或者实现Runnable接口:

    // 1
    public class IAmAThread extends Thread {
        public IAmAThread() {
            super("IAmAThread");
        }
    
        @Override
        public void run() {
    
    // your code (sequence of instructions)
        }
    }
    // to execute this sequence of instructions in a separate thread.
    new IAmAThread().start();
    
    // 2
    public class IAmARunnable implements Runnable {
        @Override
        public void run() {
    
    // your code (sequence of instructions)
        }
    }
    // to execute this sequence of instructions in a separate thread.
    IAmARunnable myRunnable = new IAmARunnable();
    new Thread(myRunnable).start();
    

    这两个方法基本上是一样的。第一个版本是创建一个Thread类,第二个版本是需要创建一个Runnable对象,然后也需要一个Thread类来调用它。

    Android上的线程

    无论何时启动APP,所有的组件都会运行在一个单独的线程中(默认的)——叫做主线程。这个线程主要用于处理UI的操作并为视图组件和小部件分发事件等,因此主线程也被称作UI线程(Main Thread)。除了Main Thread之外的线程都可称为Worker Thread。Main Thread主要负责控制UI页面的显示、更新、交互等。 因此所有在UI线程中的操作要求越短越好,只有这样用户才会觉得操作比较流畅。一个比较好的做法是把一些比较耗时的操作,例如网络请求、数据库操作、 复杂计算等逻辑都封装到单独的线程,这样就可以避免阻塞主线程,这样就需要用到了Android的Handler机制。

    这里划重点:Handler负责与子线程进行通讯,从而让子线程与主线程之间建立起协作的桥梁,使Android的UI更新的问题得到完美的解决

    怎么创建Handler

    既然Handler有这样的好处,那么看Handler怎么用,官方给出了两种方式创建一个Handler:

    1、使用默认的构造方法:new Handler()。
    2、使用带参的构造方法,参数是一个Runnable对象或者回调对象。

    //第一种方法
    private Handler handler = new Handler();  
       private Runnable myRunnable= new Runnable() {    
            public void run() {  
                 //一些耗时操作
            }  
        }; 
     //其他地方调用
     handler.post(xxx);
     这里就写一个post方法,实际上还有很多,诸如postDelayed、postAtTime
    //第二种方法
    Handler myHandler = new Handler() {  
              public void handleMessage(Message msg) {   
                   switch (msg.what) {   
                       //根据参数进行操作
                             break;   
                   }   
                   super.handleMessage(msg);   
              }   
         };  
      //其他地方调用
    myHandler.sendMessage(xxx);
    

    如何使用Handler

    这里使用一个简单的Demo来演示Handler的用法,界面偏简单就不贴了,直接贴代码,模拟的是点击Button执行下载,下载完成后更新UI。

    public class MainActivity extends Activity implements Button.OnClickListener {
    
        private TextView statusTextView = null;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            statusTextView = (TextView)findViewById(R.id.statusTextView);
            Button btnDownload = (Button)findViewById(R.id.btnDownload);
            btnDownload.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            DownloadThread downloadThread = new DownloadThread();
            downloadThread.start();
        }
    
        class DownloadThread extends Thread{
            @Override
            public void run() {
                try{
                    System.out.println("开始下载文件");
                    //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                    Thread.sleep(5000);
                    System.out.println("文件下载完成");
                    //文件下载完成后更新UI
                    MainActivity.this.statusTextView.setText("文件下载完成");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    

    按照以前写Java的思路的话可能会这么写,但是运行程序时候会发现控制台报错:

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

    错误的意思是只有创建View的原始线程才能更新View。出现这样错误的原因是Android中的View不是线程安全的,下面给出合理的解释:

    因为UI访问是没有加锁的,在多个线程中访问UI是不安全的,如果有多个子线程都去更新UI,会导致界面不断改变而混乱不堪。所以最好的解决办法就是只有一个线程有更新UI的权限,所以这个时候就只能有一个线程振臂高呼:放开那女孩,让我来!那么最合适的人选只能是主线程。

    来自---Android中线程那些事

    那么为了规避Android的这种机制,我们这里分别采用Handler的两种方式来实现上面的代码:

    A、使用post方式
    
    public class MainActivity extends Activity implements Button.OnClickListener {
    
        private TextView statusTextView = null;
    
        //uiHandler在主线程中创建,所以自动绑定主线程
        private Handler uiHandler = new Handler();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            statusTextView = (TextView)findViewById(R.id.statusTextView);
            Button btnDownload = (Button)findViewById(R.id.btnDownload);
            btnDownload.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            DownloadThread downloadThread = new DownloadThread();
            downloadThread.start();
        }
    
        class DownloadThread extends Thread{
            @Override
            public void run() {
                try{
                    System.out.println("开始下载文件");
                    //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                    Thread.sleep(5000);
                    System.out.println("文件下载完成");
                    //文件下载完成后更新UI
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            MainActivity.this.statusTextView.setText("文件下载完成");
                        }
                    };
                    uiHandler.post(runnable);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    
    
    B、使用sendMessage方式实现
    
    public class MainActivity extends Activity implements Button.OnClickListener {
    
        private TextView statusTextView = null;
    
        //uiHandler在主线程中创建,所以自动绑定主线程
        private Handler uiHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:
                        System.out.println("msg.arg1:" + msg.arg1);
                        System.out.println("msg.arg2:" + msg.arg2);
                        MainActivity.this.statusTextView.setText("文件下载完成");
                        break;
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            statusTextView = (TextView)findViewById(R.id.statusTextView);
            Button btnDownload = (Button)findViewById(R.id.btnDownload);
            btnDownload.setOnClickListener(this);
            System.out.println("Main thread id " + Thread.currentThread().getId());
        }
    
        @Override
        public void onClick(View v) {
            DownloadThread downloadThread = new DownloadThread();
            downloadThread.start();
        }
    
        class DownloadThread extends Thread{
            @Override
            public void run() {
                try{
                    System.out.println("开始下载文件");
                    //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                    Thread.sleep(5000);
                    System.out.println("文件下载完成");
                    //文件下载完成后更新UI
                    Message msg = new Message();
                    //虽然Message的构造函数式public的,我们也可以通过以下两种方式通过循环对象获取Message
                    //msg = Message.obtain(uiHandler);
                    //msg = uiHandler.obtainMessage();
    
                    //what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别
                    //出不同的Message,以便我们做出不同的处理操作
                    msg.what = 1;
    
                    //我们可以通过arg1和arg2给Message传入简单的数据
                    msg.arg1 = 123;
                    msg.arg2 = 321;
                    //我们也可以通过给obj赋值Object类型传递向Message传入任意数据
                    //msg.obj = null;
                    //我们还可以通过setData方法和getData方法向Message中写入和读取Bundle类型的数据
                    //msg.setData(null);
                    //Bundle data = msg.getData();
                    //将该Message发送给对应的Handler
                    uiHandler.sendMessage(msg);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    

    以上代码来自博客:Android中Handler的使用

    上面这两种形式都能达到我们的要求,在此不一一测验,注释写的很详细了,看到这里应该已经大致知道了如何使用Handler,但是我想我们应该远远不满足于此,下一篇博客将带着大家从源码一起看看Handler机制到底是怎么实现的。
    Handler机制从入门到放弃(二)

    相关文章

      网友评论

      • Jics:希望写写放弃的理由
        李牧羊:@Jics 看一堆源码看的想吐

      本文标题:Handler机制从入门到放弃(一)

      本文链接:https://www.haomeiwen.com/subject/ozjuuttx.html