Android线程间通讯机制

当android应用程序运行时,一个主线程被建立(也称做UI线程),此线程主要负责处理UI相关的事件,因为Android采用UI单线程模型,因此只能在主线程中对UI元素进行操做,若是在非UI线程直接对UI进行了操做,则会报错,另外,对于运算量较大的操做和IO操做,咱们须要新开线程来处理这些工做,以避免阻塞UI线程,子线程与主线程之间是怎样进行通讯的呢?此时就要采用消息循环机制(Looper)与Handler进行处理。java

1、基本概念

Looper:每个线程均可以产生一个Looper,用来管理线程的Message,Looper对象会创建一个MessgaeQueue数据结构来存放message。android

Handler:与Looper沟通的对象,能够push消息或者runnable对象到MessgaeQueue,也能够从MessageQueue获得消息。数据结构

查看其构造函数:ide

Handler()函数

Default constructor associates this handler with the queue for the current thread.//如不指定Looper参数则默认利用当前线程的Looper建立oop

Handler(Looper looper)post

Use the provided queue instead of the default one.//使用指定的Looper对象建立Handlerthis

线程A的Handler对象引用能够传递给别的线程,让别的线程B或C等能送消息来给线程A。spa

线程A的Message Queue里的消息,只有线程A所属的对象能够处理。线程

注意:Android里没有global的MessageQueue,不一样进程(或APK之间)不能经过MessageQueue交换消息。


2、Handler经过Message通讯的基本方式

使用Looper.myLooper能够取得当前线程的Looper对象。

使用mHandler = new Handler(Looper.myLooper()); 可产生用来处理当前线程的Handler对象。

使用mHandler = new Handler(Looper.getMainLooper()); 可诞生用来处理main线程的Handler对象。

使用Handler传递消息对象时将消息封装到一个Message对象中,Message对象中主要字段以下:

Message对象能够经过Message类的构造函数得到,但Google推荐使用Message.obtain()方法得到,该方法会从全局的对象池里返回一个可复用的Messgae实例,API中解释以下:

Message()

Constructor (but the preferred way to get a Message is to call Message.obtain()).

Handler发出消息时,既能够指定消息被接受后立刻处理,也能够指定通过必定时间间隔以后被处理,如sendMessageDelayed(Message msg, long delayMillis),具体请参考API。

Handler消息被发送出去以后,将由handleMessage(Message msg)方法处理。

注意:在Android里,新诞生一个线程,并不会自动创建其Message Loop能够经过调用Looper.prepare()为该线程创建一个MessageQueue,再调用Looper.loop()进行消息循环。

下面举例说明:

在main.xml中定义两个button及一个textview

public class HandlerTestActivity extends Activity implements OnClickListener{

    public TextView tv;
    private myThread myT;
    Button bt1, bt2;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        bt1 = (Button)findViewById(R.id.a);
        bt2 = (Button)findViewById(R.id.b);
        tv = (TextView)findViewById(R.id.tv);
        bt1.setId(1);//为两个button设置ID,此ID用于后面判断是哪一个button被按下
        bt2.setId(2);
        bt1.setOnClickListener(this);//增长监听器
        bt2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
       switch(v.getId()){//按键事件响应,若是是第一个按键将启动一个新线程
       case 1:
            myT = new myThread();
            myT.start();
            break;
       case 2:
            finish();
            break;
       default:
            break;
       }
    }  

    class myThread extends Thread {
        private EHandler mHandler;
        public void run() {
             Looper myLooper, mainLooper;
             myLooper = Looper.myLooper();//获得当前线程的Looper
             mainLooper = Looper.getMainLooper();//获得UI线程的Looper
             String obj;
             if(myLooper == null) { //判断当前线程是否有消息循环Looper
                  mHandler = new EHandler(mainLooper);
                  obj = "current thread has no looper!";//当前Looper为空,EHandler用mainLooper对象构造
             } else {
              mHandler = new EHandler(myLooper);//当前Looper不为空,EHandler用当前线程的Looper对象构造
              obj = "This is from current thread.";
             }

             mHandler.removeMessages(0);//清空消息队列里的内容
             Message m = mHandler.obtainMessage(1, 1, 1, obj);
             mHandler.sendMessage(m);//发送消息
           }
    }
    class EHandler extends Handler {

        public EHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) { //消息处理函数
           tv.setText((String)msg.obj);//设置TextView内容
        }
    }
}

程序运行后点击TestLooper按键,TextView输出以下,说明新建立的线程里Looper为空,也就说明了新建立的线程并不会本身创建Message Looper。

修改myThread类:

class myThread extends Thread {

    private EHandler mHandler;

    public void run() {
        Looper myLooper, mainLooper;
        Looper.prepare();
        myLooper = Looper.myLooper();
        mainLooper = Looper.getMainLooper();
        ......
        ......
        mHandler.sendMessage(m);
        Looper.loop();
    }

}

Looper.prepare为当前线程建立一个Message Looper,Looper.loop()开启消息循环。这样修改是OK呢?

答案是否认的!运行时Logcat将抛出CalledFromWrongThreadException异常错误,提示以下:

意思就是说“只有原始建立这个视图层次的线程才能修改它的视图”,本例中的TextView是在UI线程(主线程)中建立,所以,myThread线程不能修改其显示内容!

通常的作法是在子线程里获取主线程里的Handler对象,而后经过该对象向主线程的消息队列里发送消息,进行通讯。

若是子线程想修改主线程的UI,能够经过发送Message给主线程的消息队列,主线程经行判断处理再对UI经行操做,具体能够参考以前的代码。


3、Handler经过runnable通讯的基本方式

咱们能够经过Handler的post方法实现线程间的通讯,API中关于post方法说明以下

public final boolean post (Runnable r)

Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.

Post方法将安排runnable对象在主线程的某个位置运行,可是并不会开启一个新的线程,验证代码以下:

public class HandlerTestActivity extends Activity {

    private Handler handlerTest;

    Runnable runnableTest = new Runnable() {

       public void run() {
           String runID = String.valueOf(Thread.currentThread().getId());//输出Runnable 线程的ID号
           Log.v("Debug",runID);
       }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        handlerTest = new Handler();
        String mainID = String.valueOf(Thread.currentThread().getId());
        Log.v("Debug",mainID);//输出主线程的ID号
        handlerTest.post(runnableTest);
    }
}

Logcat里输出以下:

说明只是把runnable里的run方法放到UI线程里运行,并不会建立新线程

所以咱们能够在子线程中将runnable加入到主线程的MessageQueue,而后主线程将调用runnable的方法,能够在此方法中更新主线程UI。

将以前的代码修改以下:

public class HandlerTestActivity extends Activity {

    private Handler handlerTest;
    private TextView tv;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        tv = (TextView)findViewById(R.id.tv);//TextView初始化为空
        handlerTest = new Handler();
        myThread myT = new myThread();
        myT.start();//开启子线程
    }

    class myThread extends Thread{
        public void run(){
            handlerTest.post(runnableTest);//子线程将runnable加入消息队列
        }
    }

    Runnable runnableTest = new Runnable() {

       public void run() {
           tv.setText("此信息由子线程输出!");
       }
    };

}

至关于在主线程中调用了runnalbe的run方法,更改了TextView的UI!

相关文章
相关标签/搜索