zoukankan      html  css  js  c++  java
  • Android-消息处理学习总结(Handler,Looper)

    参考资料:

    http://www.cnblogs.com/qlky/p/5657924.html

    http://blog.csdn.net/guolin_blog/article/details/9991569

    http://blog.csdn.net/gh102/article/details/7191486

    http://www.cnblogs.com/plokmju/p/android_Handler.html

    http://www.jianshu.com/p/02962454adf7

    http://www.jianshu.com/p/ac50ba6ba3a2

    可以看到有这么多的资料,内容也很多,看的眼花缭乱。我决定自己总结一下,从最简单的开始,再慢慢补细节。

    Handler

    Handler,它直接继承自Object,一个Handler允许发送和处理Message或者Runnable对象,并且会关联到主线程的MessageQueue中。每个Handler具有一个单独的线程,并且关联到一个消息队列的线程,就是说一个Handler有一个固有的消息队列。当实例化一个Handler的时候,它就承载在一个线程和消息队列的线程,这个Handler可以把Message或Runnable压入到消息队列,并且从消息队列中取出Message或Runnable,进而操作它们。

    • 作用

    android不允许在主线程里做耗时操作,如网络操作,以此来避免ANR。

    ANR(Application Not Responding)

    http://baike.baidu.com/link?url=rLzKRNkjt79XITQKhRXp32alhsuKEt2FoHPw3vuB2UlEvyKOZwnEh4OYoPy4_fwO6zPPECXWre4ycip4mB0LOq

    Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者以数据库操作为例,通过异步请求的方式)来完成。

    默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。

    因此如果想进行上述的操作,应该开启一个子线程。而在子线程中,android不允许进行UI操作。如果想在子线程中进行UI操作,就可以使用Handler开启UI线程。

    • 用法

    Handler有两种用法:

    • Post:Post允许把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。
    • sendMessage:sendMessage允许把一个包含消息数据的Message对象压入到消息队列中。它的方法有:sendEmptyMessage(int)、sendMessage(Message)、sendMessageAtTime(Message,long)、sendMessageDelayed(Message,long)。

    具体看这里:http://www.cnblogs.com/plokmju/p/android_Handler.html

    两个实例

    第一个是post的,处理在子线程修改UI

    public class HandlerPostActivity1 extends Activity {
        private Button btnMes1,btnMes2;
        private TextView tvMessage;
        // 声明一个Handler对象
        private static Handler handler=new Handler();
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.message_activity);        
            
            btnMes1=(Button)findViewById(R.id.btnMes1);
            btnMes2=(Button)findViewById(R.id.btnMes2);
            tvMessage=(TextView)findViewById(R.id.tvMessage);
            btnMes1.setOnClickListener(new View.OnClickListener() {
                
                @Override
                public void onClick(View v) {
                    // 新启动一个子线程
                    new Thread(new Runnable() {                    
                        @Override
                        public void run() {
                            // tvMessage.setText("...");
                            // 以上操作会报错,无法再子线程中访问UI组件,UI组件的属性必须在UI线程中访问
                            // 使用post方式修改UI组件tvMessage的Text属性
                            handler.post(new Runnable() {                    
                                @Override
                                public void run() {
                                    tvMessage.setText("使用Handler.post在工作线程中发送一段执行到消息队列中,在主线程中执行。");                        
                                }
                            });                                
                        }
                    }).start();
                }
            });
        }
    }

    第二个是Message的,子线程改变UI

    public class HandlerMessageActivity2 extends Activity {
        private Button btn1, btn2, btn3, btn4,btn5;
        private static TextView tvMes;
        private static Handler handler = new Handler() {
            @Override
            public void handleMessage(android.os.Message msg) {
                if (msg.what == 3||msg.what==5) {
                    tvMes.setText("what=" + msg.what + ",这是一个空消息");
                } else {
                    tvMes.setText("what=" + msg.what + "," + msg.obj.toString());
                }
    
            };
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            super.onCreate(savedInstanceState);
            setContentView(R.layout.message_activity2);
            tvMes = (TextView) findViewById(R.id.tvMes);
            btn1 = (Button) findViewById(R.id.btnMessage1);
            btn2 = (Button) findViewById(R.id.btnMessage2);
            btn3 = (Button) findViewById(R.id.btnMessage3);
            btn4 = (Button) findViewById(R.id.btnMessage4);
            btn5 = (Button) findViewById(R.id.btnMessage5);
    
            btn1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 使用Message.Obtain+Hander.sendMessage()发送消息
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Message msg = Message.obtain();
                            msg.what = 1;
                            msg.obj = "使用Message.Obtain+Hander.sendMessage()发送消息";
                            handler.sendMessage(msg);
                        }
                    }).start();
                }
            });
    
            btn2.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    // 使用Message.sendToTarget发送消息
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Message msg = Message.obtain(handler);
                            msg.what = 2;
                            msg.obj = "使用Message.sendToTarget发送消息";
                            msg.sendToTarget();
                        }
                    }).start();
                }
            });
    
            btn3.setOnClickListener(new View.OnClickListener() {
                // 发送一个延迟消息
                @Override
                public void onClick(View v) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            handler.sendEmptyMessage(3);
                        }
                    }).start();
                }
            });
    
            btn4.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Message msg = Message.obtain();
                            msg.what =4;
                            msg.obj = "使用Message.Obtain+Hander.sendMessage()发送延迟消息";
                            handler.sendMessageDelayed(msg, 3000);
                        }
                    }).start();
                }
            });
            
            btn5.setOnClickListener(new View.OnClickListener() {
                // 发送一个延迟的空消息
                @Override
                public void onClick(View v) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            handler.sendEmptyMessageDelayed(5, 3000);
                        }
                    }).start();
                }
            });
        }
    }
    • 原理

     从作用可以知道,需要解决在子线程修改UI的问题。而UI只能在主线程修改,所以问题就变成了怎么让子线程随时能告诉主线程该怎么做。

    android给出的方案是这样的:

    1.因为代码执行完会结束,而主线程需要随时响应不能结束,所以主线程需要在一个死循环里面等待消息:Looper

    2.主线程需要在开启死循环前,设立一个接受和处理消息的机制(包括跳出循环的消息):Handler

    3.需要规定消息的种类和载体:Message

    4.同一线程在同一时间只能处理一个消息,所以需要保存消息的顺序和时间,一条条拿出来处理:MessageQueue

    5.由于同一进程中线程和线程之间资源是共享的,所以任何线程都可以获取到MessageQueue实例,然后向主线程发送消息

     所以Handler实际上就是主线程接收和处理消息的一个封装。在子线程new Handler()时帮你获得MessageQueue实例,并封装发送消息的方法。在主线程MessageQueue处理消息时又封装了Handler来处理消息。

    一个最标准的异步处理线程(也是将普通线程转成Looper线程的方法):

    class LooperThread extends Thread {  
          public Handler mHandler;  
      
          public void run() {  
              Looper.prepare();  
      
              mHandler = new Handler() {  
                  public void handleMessage(Message msg) {  
                      // process incoming messages here  
                  }  
              };  
      
              Looper.loop();  
          }  
      }

    三种在子线程改变UI的方法

    1. Handler的post()方法

    2. View的post()方法

    3. Activity的runOnUiThread()方法

     

    Handler.post

     我们先来看下Handler中的post()方法,代码如下所示:

    public final boolean post(Runnable r)  
    {  
       return  sendMessageDelayed(getPostMessage(r), 0);  
    }  

    其实就是sendMessageDelayed(),再看getPostMessage()

    private final Message getPostMessage(Runnable r) {  
        Message m = Message.obtain();  
        m.callback = r;  
        return m;  
    }  

    dispatchMessage()方法中有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法

    private final void handleCallback(Message message) {  
        message.callback.run();  
    }  

    所以所谓callback就是子线程我们创建的runnable,然后在主线程里执行它的run方法

     这时再看post用法就懂了:

    public class MainActivity extends Activity {  
      
        private Handler handler;  
      
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            handler = new Handler();  
            new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    handler.post(new Runnable() {  
                        @Override  
                        public void run() {  
                            // 在这里进行UI操作  
                        }  
                    });  
                }  
            }).start();  
        }  
    }

    View中的post()方法

    public boolean post(Runnable action) {  
        Handler handler;  
        if (mAttachInfo != null) {  
            handler = mAttachInfo.mHandler;  
        } else {  
            ViewRoot.getRunQueue().post(action);  
            return true;  
        }  
        return handler.post(action);  
    }  

    用的就是handler的post,不解释了

    Activity中的runOnUiThread()方法

    public final void runOnUiThread(Runnable action) {  
        if (Thread.currentThread() != mUiThread) {  
            mHandler.post(action);  
        } else {  
            action.run();  
        }  
    }  

    如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。还有什么会比这更清晰明了的吗?

    为什么要用Message.obtain()而不是new Message();

    Message Pool消息池
    public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }

    我们通过obtain方法取出一条消息的时候,如果发现当前的消息池不为空,那就直接重复利用Message(已经被创建过和handle过的);如果为空就重新new 一个消息。这就是一种享元设计模式的概念。例如在游戏里面,发子弹,如果一个子弹是一个对象,一按下按键就发很多个子弹,那么这时候就需要利用享元模式去循环利用了。

     

     

    Handler与Android四大组件生命周期

    http://www.jianshu.com/p/ac50ba6ba3a2

    除了客户端的handler外,还有系统handler,用来处理系统的操作消息:比如启动Activity等四大组件

    一小段代码,应用程序的入口:

    public final class ActivityThread {
        public static final void main(String[] args) {
            ......
            Looper.prepareMainLooper();
            ......
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {    
                sMainThreadHandler = thread.getHandler();
            }
            ......
            Looper.loop();
            ......
        }
    }
    • ActivityThread并不是一个线程,它并没有继承Thread,而只是一个普通的类public final class ActivityThread{...}ActivityThread的构造函数并没有做什么事只是初始化了资源管理器。
    • thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,该Binder线程会通过想 HandlerMessage发送给主线程,之后讲)
    • 在Looper.loop()中进入死循环

    插入一个问题:

    主线程是UI线程和用户交互的线程,优先级应该很高,主线程的死循环一直运行是不是会特别消耗CPU资源吗?App进程的其他线程怎么办?

    • 这基本是一个类似生产者消费者的模型,简单说如果在主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()方法里,这时候主线程会释放CPU资源进入休眠状态,直到有下个消息进来时候就会唤醒主线程,在2.2 版本以前,这套机制是用我们熟悉的线程的wait和notify 来实现的,之后的版本涉及到Linux pipe/epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作。原理类似于I/O,读写是堵塞的,不占用CPU资源。

    系统Handler

    final H mH = new H();

    在new ActivityThread的时候,系统的Handler就就初始化了,这是一种饿加载的方法,也就是在类被new的时候就初始化成员变量了。另外还有一种懒加载,就是在需要的时候才去初始化,这两种方式在单例设计模式里面比较常见。

    系统是怎么发消息给主线程的,主线程是怎么处理这些个消息的?

    在准备启动一个Activity的时候,系统服务进程下的ActivityManagerService(简称AMS)线程会通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程下的Binder线程最终调用ActivityThread类下面的scheduleLaunchActivity方法来准备启动Activity

    注:Binder线程:具体是指ApplicationThread,在App进程中接受系统进程传递过来的信息的线程(在主线程进入死循环之前创建了这个线程)。

    看下scheduleLaunchActivity方法:

    //这个方法不是在主线程调用,是Binder线程下调用的
      public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                    ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                    CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                    int procState, Bundle state, PersistableBundle persistentState,
                    List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                    boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
    
                updateProcessState(procState, false);
    
                ActivityClientRecord r = new ActivityClientRecord();
    
                ....
    
                sendMessage(H.LAUNCH_ACTIVITY, r);
      }

    把启动一些信息封装成ActivityClientRecord之后,最后一句调用sendMessage(H.LAUNCH_ACTIVITY, r);

    再看这个方法:

    private void sendMessage(int what, Object obj) {
            sendMessage(what, obj, 0, 0, false);
        }
    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
             Message msg = Message.obtain();
            msg.what = what;
            msg.obj = obj;
            msg.arg1 = arg1;
            msg.arg2 = arg2;
            if (async) {
                msg.setAsynchronous(true);
            }
            mH.sendMessage(msg);
        }

    很清楚了,APP下的Binder就是用系统handler来启动activity的

    总结

  • 相关阅读:
    maven 仓库配置 pom中repositories属性
    Spring Boot集成持久化Quartz定时任务管理和界面展示
    gradle使用总结
    sqlserver 分页
    MyBatis特殊字符转义
    Mybatis中#{}和${}传参的区别及#和$的区别小结
    Markdown 手册
    Spring boot——logback.xml 配置详解(四)<filter>
    Spring boot——logback.xml 配置详解(三)<appender>
    Spring boot——logback.xml 配置详解(二)
  • 原文地址:https://www.cnblogs.com/qlky/p/6665329.html
Copyright © 2011-2022 走看看