Android消息机制

来源:互联网 发布:教师网络研修的好处 编辑:程序博客网 时间:2024/06/05 03:55

参考:

浅析Android中的消息机制

Android异步消息处理机制完全解析,带你从源码的角度彻底理解

基本介绍:

大家都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃。相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。

先来看一段典型的代码:

Android系统中的视图组件并不是线程安全的,如果要更新视图,必须在主线程中更新,不可以在子线程中执行更新的操作。既然这样,我们就在子线程中通知主线程,让主线程做更新操作吧。那么,我们如何通知主线程呢?我们需要使用到Handler对象。

public class MainActivity extends Activity implements View.OnClickListener {    private static final int COMPLETED = 0;    private TextView stateText;    private Button btn;    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            if (msg.what == COMPLETED) {                stateText.setText("completed");            }        }    };    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        stateText = (TextView) findViewById(R.id.tv);        btn = (Button) findViewById(R.id.btn);        btn.setOnClickListener(this);    }    @Override    public void onClick(View v) {        new WorkThread().start();    }    //工作线程      private class WorkThread extends Thread {        @Override        public void run() {            //......处理比较耗时的操作              //处理完成后给handler发送消息              Message msg = new Message();            msg.what = COMPLETED;            handler.sendMessage(msg);        }    }} 

通过上面这种方式,我们就可以解决线程安全的问题,把复杂的任务处理工作交给子线程去完成,然后子线程通过handler对象告知主线程,由主线程更新视图,这个过程中消息机制起着重要的作用。

熟悉Windows编程的朋友知道Windows程序是消息驱动的,并且有全局的消息循环系统。Google参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制。Android通过Looper、Handler来实现消息循环机制。Android的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环。

Android系统中的Looper负责管理线程的消息队列和消息循环。通过Looper.myLooper()得到当前线程的Looper对象,通过Looper.getMainLooper()得到当前进程的主线程的Looper对象。

前面提到,Android的消息队列和消息循环都是针对具体线程的,一个线程可以存在一个消息队列和消息循环,特定线程的消息只能分发给本线程,不能跨线程和跨进程通讯。但是创建的工作线程默认是没有消息队列和消息循环的,如果想让工作线程具有消息队列和消息循环,就需要在线程中先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。下面是我们创建的工作线程

class WorkThread extends Thread {    public Handler mHandler;    public void run() {        Looper.prepare();        mHandler = new Handler() {            public void handleMessage(Message msg) {                // 处理收到的消息            }        };        Looper.loop();    }}

这样一来,我们创建的工作线程就具有了消息处理机制了。

那么,为什么前边的示例中,我们怎么没有看到Looper.prepare()和Looper.loop()的调用呢?原因在于,我们的Activity是一个UI线程,运行在主线程中,Android系统会在Activity启动时为其创建一个消息队列和消息循环。

一个Activity中可以创建出多个工作线程,如果这些线程把他们消息放入Activity主线程的消息队列中,那么消息就会在主线程中处理了。因为主线程一般负责视图组件的更新操作,对于不是线程安全的视图组件来说,这种方式能够很好的实现视图的更新。

那么,子线程如何把消息放入主线程的消息队列中呢?只要Handler对象以主线程的Looper创建,那么当调用Handler的sendMessage方法,系统就会把消息主线程的消息队列,并且将会在调用handleMessage方法时处理主线程消息队列中的消息。

对于子线程访问主线程的Handler对象,你可能会问,多个子线程都访问主线程的Handler对象,发送消息和处理消息的过程中会不会出现数据的不一致呢?答案是Handler对象不会出现问题,因为Handler对象管理的Looper对象是线程安全的,不管是添加消息到消息队列还是从消息队列中读取消息都是同步保护的,所以不会出现数据不一致现象。

那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。

Handler机制的原理 :

andriod提供了 Handler和 Looper来满足线程间的通信。

Handler 先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(Message Exchange)。

1)Looper:一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。

2)Handler:你可以构造Handler对象来与Looper沟通,以便push新消息到Message Queue里;或者接收Looper从Message Queue取出)所送来的消息。

3) Message Queue(消息队列):用来存放线程放入的消息。

4)线程:UI thread通常就是main thread,而Android启动程序时会替它建立一个Message Queue

子线程中更新UI的方式:

另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:

1. Handler的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();    }}  

2. Viewpost()方法

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()方法

3. ActivityrunOnUiThread()方法

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

如果当前的线程不等于UI线程(主线程),就去调用Handlerpost()方法,否则就直接调用Runnable对象的run()方法。

Android进行异步更新UI的四种方式

根据上面的描述总结出来:

1.使用Handler消息传递机制(依附主线程);

2.使用AsyncTask异步任务;

3.使用runOnUiThread(action)方法;

4.使用Handlerpost(Runnabel r)方法;

 

1>使用Handler消息传递机制:

public class MainActivity extends Activity {    private TextView tv;    Handler handler = new Handler()    {        public void handleMessage(android.os.Message msg) {            if(msg.what==0x123)            {                tv.setText("更新后的TextView");            }        };    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        new MyThread().start();    }    class MyThread extends Thread    {        @Override        public void run() {            //延迟两秒更新            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            handler.sendEmptyMessage(0x123);        }    }}

2>使用AsyncTask异步任务:

public class MainActivity extends Activity {    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        new Yibu().execute();    }    class Yibu extends AsyncTask<String, String, String>    {        @Override        protected String doInBackground(String... params) {            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            return null;        }        @Override        protected void onPostExecute(String result) {            // TODO Auto-generated method stub            tv.setText("更新后的TextView");        }    }}

3>使用runOnUiThread(action)方法:

public class MainActivity extends Activity {    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        new MyThread().start();    }    class MyThread extends Thread    {        @Override        public void run() {            runOnUiThread(new Runnable() {                @Override                public void run() {                    // TODO Auto-generated method stub                    try {                        //延迟两秒更新                        Thread.sleep(2000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    tv.setText("更新后的TextView");                }            });        }    }}

4>使用Handlerpost(Runnabel r)方法:

public class MainActivity extends Activity {    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        Handler handler = new Handler();        handler.post(new Runnable(){            @Override            public void run() {                try {                    //延迟两秒更新                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                tv.setText("更新后的TextView");            }        });    }}

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

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

原来这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息,我们来看下这个方法的源码:

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

在这个方法中将消息的callback字段的值指定为传入的Runnable对象。咦?这个callback字段看起来有些眼熟啊,喔!在HandlerdispatchMessage()方法中原来有做一个检查,如果Messagecallback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧:

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

也太简单了!竟然就是直接调用了一开始传入的Runnable对象的run()方法。

public void dispatchMessage(Message msg) {      if (msg.callback != null) {          handleCallback(msg);      } else {          if (mCallback != null) {              if (mCallback.handleMessage(msg)) {                  return;              }          }          handleMessage(msg);      }  }  

面试问题

1.Handler是怎么获取looper对象的,messageQueue是怎么获取message的,死循环还是轮询 

   在考察源码的熟悉程度。(http://blog.csdn.net/guolin_blog/article/details/9991569郭哥的这个源码分析还需仔细看看)

   Handler的无参构造函数如下所示:

public Handler() {    if (FIND_POTENTIAL_LEAKS) {        final Class<? extends Handler> klass = getClass();        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&                (klass.getModifiers() & Modifier.STATIC) == 0) {            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +                    klass.getCanonicalName());        }    }    mLooper = Looper.myLooper();    if (mLooper == null) {        throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");    }    mQueue = mLooper.mQueue;    mCallback = null;}

可以看到,在第10行调用了Looper.myLooper()方法获取了一个Looper对象,如果Looper对象为空,则会抛出一个运行时异常,提示的错误正是 Can't create handler inside thread that has not called Looper.prepare()

线程中的Handler也没有调用Looper.prepare()方法,为什么就没有崩溃呢?细心的朋友我相信都已经发现了这一点,这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。

那出队操作是在哪里进行的呢?这个就需要看一看Looper.loop()方法的源码了,如下所示:

public static final void loop() {    Looper me = myLooper();    MessageQueue queue = me.mQueue;    while (true) {        Message msg = queue.next(); // might block          if (msg != null) {            if (msg.target == null) {                return;            }            if (me.mLogging!= null) me.mLogging.println(                    ">>>>> Dispatching to " + msg.target + " "                            + msg.callback + ": " + msg.what            );            msg.target.dispatchMessage(msg);            if (me.mLogging!= null) me.mLogging.println(                    "<<<<< Finished to    " + msg.target + " "                            + msg.callback);            msg.recycle();        }    }}

可以看到,这个方法从第4行开始,进入了一个死循环,然后不断地调用的MessageQueuenext()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。

2.子线程中是否可以创建handler?

 我们在平时开发的过程中,经常是子线程往主线程中发消息,让主线程更新UI。但是根据具体的项目需求,也可能会要求让你在主线程中往子线程中发消息。 

public class MainActivity extends Activity implements OnClickListener {    public static final int UPDATE_TEXT = 1;    private TextView tv;    private Button btn;    private Handler handler;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        btn = (Button) findViewById(R.id.btn);        btn.setOnClickListener(this);        new Thread(new Runnable() {            @Override            public void run() {                //1、准备Looper对象                Looper.prepare();                //2、在子线程中创建Handler                handler = new Handler() {                    @Override                    public void handleMessage(Message msg) {                        super.handleMessage(msg);                        Log.i("handleMessage:", Thread.currentThread().getName());                        Log.i("后台输出", "收到了消息对象");                    }                };                //3、调用Looper的loop()方法,取出消息对象                Looper.loop();            }        }).start();    }    @Override    public void onClick(View v) {        Log.i("onClick:", Thread.currentThread().getName());        switch (v.getId()) {            case R.id.btn:                Message msg = handler.obtainMessage();                handler.sendMessage(msg);                break;            default:                break;        }    }}

上方的第29行至41行代码:这是MainThread中发送消息,在子线程中接收消息的固定写法。上面的三个步骤再重复一下:

· 准备Looper对象

· WorkerThread当中生成一个Handler对象

· 调用Looperloop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handlerhandleMessage()方法,处理该消息对象;如果消息队列中没有对象,则该线程阻塞

注意,此时handleMessage()方法是在子线程中运行的

后台运行效果:


小小地总结一下:

  首先执行Looperprepare()方法,这个方法有两个作用:一是生成Looper对象,二是把Looper对象和当前线程对象形成键值对(线程为键),存放在ThreadLocal当中,然后生成handler对象,调用LoopermyLooper()方法,得到与Handler所对应的Looper对象,这样的话,handlerlooper、消息队列就形成了一一对应的关系,然后执行上面的第三个步骤,即Looper在消息队列当中循环的取数据。

另外,在本文最开头的第一段中,我们在主线程中创建Handler也没有调用Looper.prepare()方法,为什么就没有崩溃呢?,这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:

public static void main(String[] args) {        SamplingProfilerIntegration.start();        CloseGuard.setEnabled(false);        Environment.initForCurrentUser();        EventLogger.setReporter(new EventLoggingReporter());        Process.setArgV0("<pre-initialized>");        Looper.prepareMainLooper();        ActivityThread thread = new ActivityThread();        thread.attach(false);        if (sMainThreadHandler == null) {           sMainThreadHandler = thread.getHandler();        }        AsyncTask.init();        if (false) {            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));        }        Looper.loop();        throw new RuntimeException("Main thread loop unexpectedly exited");  }  

 上方代码中,可以看到,在第7行调用了Looper.prepareMainLooper()方法,而这个方法又会再去调用Looper.prepare()方法,代码如下所示:

 public static final void prepareMainLooper() {       prepare();       setMainLooper(myLooper());       if (Process.supportsProcesses()) {           myLooper().mQueue.mQuitAllowed = false;       }   } 

在主线程中可以直接创建Handler对象,而在子线程中需要先调用Looper.prepare()才能创建Handler对象。

3.为什么调用Looper.prepare()就在当前线程关联了一个Looper对象???

每个线程只能有一个Looper对象,Looper.prepare()方法内部有调用sThreadLocal.set(new Looper());保存了一个Looper对象在本地线程变量中,可以理解成一个Map,最终new Handler的时候会去sThreadLocal里去取出这个Looper,就是这么关联上的。

Looper.myLooper()方法获取了一个Looper对象,如果Looper对象为空,则会抛出一个运行时异常,提示的错误正是 Can't create handler inside thread that has not called Looper.prepare()!那什么时候Looper对象才可能为空呢?这就要看看Looper.myLooper()中的代码了,如下所示:

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

这个方法非常简单,就是从sThreadLocal对象中取出Looper。如果sThreadLocal中有Looper存在就返回Looper,如果没有Looper存在自然就返回空了。因此你可以想象得到是在哪里给sThreadLocal设置Looper了吧,当然是Looper.prepare()方法!我们来看下它的源码:

public static final void prepare() {      if (sThreadLocal.get() != null) {          throw new RuntimeException("Only one Looper may be created per thread");      }      sThreadLocal.set(new Looper());  }  

可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。

其他

参考:
https://www.zhihu.com/question/34652589/answer/90344494(作者:Gityuan

(1).Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

(2).没看见哪里有相关代码为这个死循环准备了一个新线程去运转?

(3).Activity的生命周期这些方法这些都是在主线程里执行的吧,那这些生命周期方法是怎么实现在死循环体外能够执行起来的?


----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(1) Android中为什么主线程不会因为Looper.loop()里的死循环卡死?


这里涉及线程,先说说说进程/线程,进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。

线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片

有了这么准备,再说说死循环问题:

对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。

真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

(2) 没看见哪里有相关代码为这个死循环准备了一个新线程去运转? 

事实上,会在进入死循环之前便创建了新binder线程,在代码ActivityThread.main()中:public static void main(String[] args) {        ....        //创建Looper和MessageQueue对象,用于处理主线程的消息        Looper.prepareMainLooper();        //创建ActivityThread对象        ActivityThread thread = new ActivityThread();         //建立Binder通道 (创建新线程)        thread.attach(false);        Looper.loop(); //消息循环运行        throw new RuntimeException("Main thread loop unexpectedly exited");    }

thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程,具体过程可查看startService流程分析,这里不展开说,简单说Binder用于进程间通信,采用C/S架构。关于binder感兴趣的朋友,可查看我回答的另一个知乎问题:
为什么Android要采用Binder作为IPC机制? - Gityuan的回答

另外,ActivityThread实际上并非线程,不像HandlerThread类,ActivityThread并没有真正继承Thread类,只是往往运行在主线程,该人以线程的感觉,其实承载ActivityThread的主线程就是由Zygote fork而创建的进程。

主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见Android消息机制1-Handler(Java层),此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

(3) Activity的生命周期是怎么实现在死循环体外能够执行起来的?


ActivityThread的内部类H继承于Handler,通过handler消息机制,简单说Handler机制用于同一个进程的线程间通信。

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:
H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。

比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。 上述过程,我只挑核心逻辑讲,真正该过程远比这复杂。

主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程,请看接下来的内容:
最后,从进程与线程间通信的角度,通过一张图加深大家对App运行过程的理解:



system_server进程是系统进程java framework框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy(简称ATP),ActivityManagerService(简称AMS),这个两个服务都运行在system_server进程的不同线程中,由于ATP和AMS都是基于IBinder接口,都是binder线程,binder线程的创建与销毁都是由binder驱动来决定的。

App进程则是我们常说的应用程序,主线程主要负责Activity/Service等组件的生命周期以及UI相关操作都运行在这个线程; 另外,每个App进程中至少会有两个binder线程 ApplicationThread(简称AT)和ActivityManagerProxy(简称AMP),除了图中画的线程,其中还有很多线程,比如signal catcher线程等,这里就不一一列举。

Binder用于不同进程之间通信,由一个进程的Binder客户端向另一个进程的服务端发送事务,比如图中线程2向线程4发送事务;而handler用于同一个进程中不同线程的通信,比如图中线程4向主线程发送消息。

结合图说说Activity生命周期,比如暂停Activity,流程如下:

 1.线程1AMS中调用线程2ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)

 2.线程2通过binder传输到App进程的线程4

 3.线程4通过handler消息机制,将暂停Activity的消息发送给主线程;

 4.主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给ActivityThread.H.handleMessage()方法,再经过方法的调用,最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。

0 0
原创粉丝点击