Android中的多线程本质上也是Java的多线程,同时添加了一些不同的特性和使用的场景,其中最主要的区别就是Android的主线程与子线程的区分:Android中的线程可以分为主线程(又叫UI线程)和子线程,主线程负责运行四大组件并与用户实现交互,需要保持较高的反应速度,所以主线程不允许进行耗时的操作(比如说网络请求和访问),否则容易出现ANR现象;而子线程的作用就是完成耗时操作,确保主线程的反应速度。
在讲解异步消息处理机制前,先来讲个例子: 新建一个Android空项目,修改activity_main.xml中的代码,具体如下:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/sendMessage" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="sendMessage()" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>接下来修改MainActivity:
package com.example.threaddemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private TextView text; private Button but_send; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text=(TextView)findViewById(R.id.text); but_send=(Button)findViewById(R.id.sendMessage); but_send.setOnClickListener(this); } @Override public void onClick(View v){ switch(v.getId()){ case R.id.sendMessage: new Thread() { @Override public void run() { text.setText("sendMessage()"); } }.start(); break; default: break; } } }程序运行后结果: 可以很容易地看出,该例子实现点击sendMessage按钮来打开一个线程将屏幕中的Hello World!改为sendMessage()。代码的逻辑很简单,只不过我们是在子线程中更新UI的。当点击sendMessage按钮,你会发现程序崩溃了,我们看下错误报告,如下图所示: 可以看出,Android确实是不允许在子进程中进行UI操作的。但有的时候,我们在子进程中执行一些耗时任务时必须更改UI内容,这该怎么么办呢? 事实上,对于这种情况Android提供了一套异步消息的处理机制,完美地解决了在子进程中进行UI操作的问题。
Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper,每个线程只能有一个MessageQueue和Looper,可以有多个Hanlder。 1.Message: Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间进行数据交换。除了 what 字段,还可以使用 arg1 和 arg2 来携带整型数据,使用 obj 来携带 Object 数据。 2、Handler: Handler主要用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法,而发出的消息经过一系列的处理后,最终会传递到Handler的handleMessage()方法中。 3、MessageQueue: MessageQueue是消息队列,主要用于存放所有通过Handler发送的消息。这部分消息会一直存放在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue。 4、Looper: 用于管理 MessageQueue 队列,调用 Looper.loop() 方法之后,就会进入无限循环中,每当发现 MessageQueue 存在一条消息,就会将它取出,并传递到 Handler 的 handleMessage() 方法中。每个线程中只会有一个 Looper 对象。 了解了Message、Handler、MessageQueue和Looper的基本概念后,我们再通过图例来更清晰地理解异步消息处理,如下图所示: 可以清晰地看到整个异步消息的处理过程。首先需要在UI县城中创建一个Handler对象,然后在子线程中调用Handle的sendMessage()方法,接着这个消息会存放在UI线程的MessageQueue中,通过Looper对象取出MessageQueue中的消息,最后分发会Handler的handleMessage()方法中。
修改MainActivity:
package com.example.threaddemo; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private TextView text; private Button but_send; private Button but_post; private static final int SENDMESSAGE=1; //创建handler private Handler handler1=new Handler(){ public void handleMessage(Message msg){ switch(msg.what){//将接收到的消息与SENDMESSAGE进行匹配 case SENDMESSAGE: text.setText("sendMessage()"); break; default: break; } } }; private Handler handler2=new Handler(); private final Runnable runnable = new Runnable() { @Override public void run() { //更新UI显示 MainActivity.this.text.setText("post()"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text=(TextView)findViewById(R.id.text); but_send=(Button)findViewById(R.id.sendMessage); but_post=(Button)findViewById(R.id.post); but_post.setOnClickListener(this); but_send.setOnClickListener(this); } @Override public void onClick(View v){ switch(v.getId()){ //Handler的sendMessage()方法 case R.id.sendMessage: new Thread(new Runnable() { @Override public void run() { Message msg = new Message(); msg.what = SENDMESSAGE; handler1.sendMessage(msg); } }).start(); break; //Handler的post方法 case R.id.post: new Thread(new Runnable() { @Override public void run() { handler2.post(runnable); } }).start(); break; default: break; } } }实现效果展示: 观察sendMessage()的源码和post()的源码:
/** *Pushes a message onto the end of the message queue after all pending messages * before the current time. * 将消息添加到消息队列。 */ public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } /** * Causes the Runnable r to be added to the message queue. * 把任务对象r添加到消息队列中。 */ public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } /** *Enqueue a message into the message queue after all pending messages *post方法中调用发送延时消息的方法,把这个消息放入消息队列。 */ public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } /*post中调用的另外一个方法,我们很容易看出来,这个方法就是把任务r包装成了一个空的消息并且返回*/ private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }可以看出handler.post和handler.sendMessage本质上是没有区别的,都是发送一个消息到消息队列中,而且消息队列和handler都是依赖于同一个线程的。
Handler内存泄漏问题及其解决办法 Android用java语言编写的,其中java提供了自动的GC机制(Garbage Collection)即垃圾回收机制,采用对象引用计数的方式。在分析Handler导致的内存泄漏问题前我们自然先要了解GC是怎么回收内存的。 首先,Java的垃圾回收机制使用有向图机制,通过GC自动检查内存中的对象,如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。 private Handler handler=new Handler(){ public void handleMessage(Message msg){ switch(msg.what){//将接收到的消息与SENDMESSAGE进行匹配 case SENDMESSAGE: text.setText("sendMessage()"); break; default: break; } } };上段是关于上文例子中的Handler的使用(即使用内部类的方式来创建Handler)。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(即当完成图片下载时会加消息传给Handler),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。这样会导致程序内存溢出,程序崩溃。 对于这个问题,有两种解决方法: 1、一种是将 Handler 设置成静态内部类,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(即添加WeakReference对Activity的弱引用);
static class MyHandler extends Handler{ WeakReference<Activity> mWeakReference; public MyHandler(Activity activity) { mWeakReference=new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity=mWeakReference.get(); if(activity!=null) { if (msg.what == SENDMESSAGE) { //更新UI信息 TextView text=(TextView)activity.findViewById(R.id.text); text.setText("sendMessage"); } } } }2、第二种是在 Activity 的生命周期 onDestroy() 中调用Handler的removeCallback()方法,撤销 Handler 在 Activity 上的回调,让Activity 能成功被释放,避免内存泄漏。
@Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacks(null); }此外,除了Handler的sendMessage和post方法外,我们还可以使用View的post()方法和 Activity的runOnUiThread()方法在子线程中进行UI操作。 对于View的post()方法的测试,将原代码做一下变化: 将case语句中原来测试handler的post()方法的代码改为以下代码,同样点击post()的按钮进行测试,程序运行结果与原来测试post方法的结果一致,都将Hello World!改为了post(),这里不再演示,详细结果请参考上图。
case R.id.post: text.post(runnable); break;对于Activity的runOnUiThread()方法的测试,将原代码做一下变化: 做法请参照View的post()方法的测试,其结果也没变。
case R.id.post: this.runOnUiThread(runnable); break;我们来看看它们的源码:
/** * Causes the Runnable to be added to the message queue. * View的post方法将任务对象添加到消息队列中,该方法调用了Handler中的post()方法 */ public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } /** * Runs the specified action on the UI thread. *Activity的runOnUiThread()方法判断当前线程是否为UI线程,如果是这立即执行action任务,否则就调用Runnable对象的run()方法将action任务添加到UI线程消息队列中 * */ public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }通过对以上源码的分析,我们会发现了,不管是使用哪种方法在子线程中更新UI,其实背后的原理都是相同的,必须都要借助异步消息处理的机制来实现。 当然,在Android中基本的异步处理除了以上说明的 Handler外还有Asynctask、HandlerThread 和 IntentService,本文不再讲解,读者可自行了解。 最后,本文如有描述不当之处,欢迎大家指出,不尽感激!
参考文章链接: Android使用Handler造成内存泄露的分析及解决方法 Android中的异步消息处理 Android异步消息处理机制完全解析,带你从源码的角度彻底理解