码迷,mamicode.com
首页 > 移动开发 > 详细

Android-消息处理学习总结(Handler,Looper)

时间:2017-04-06 20:40:02      阅读:264      评论:0      收藏:0      [点我收藏+]

标签:roo   roc   加载   延迟   位图   date   button   height   否则   

 

参考资料:

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

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的

 

总结

技术分享

 

Android-消息处理学习总结(Handler,Looper)

标签:roo   roc   加载   延迟   位图   date   button   height   否则   

原文地址:http://www.cnblogs.com/qlky/p/6665329.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!