Browse » Home » Resource, Tutorial » Blog article: 线程间相互传递信息的方法

最重要的是理解实现线程间相互传递信息的目的,例如:有专门的线程来实时跟踪某些状态信息,当需要在某些Activity的线程(例如:Rendering Thread)中调用实时状态信息时,经常采用线程间相互共享数据方法。

对于没有过类似需求的开发者来讲,在这个过程中需要更加细心的了解每个步骤所代表的含义,而且必要时可以在文章后边提出困惑的问题。

这个例子的代码相比以前稍微多了一些,有必要强调阅读代码的技巧。分析别人写的代码是一件非常艰巨的任务,尤其是面对庞大的类群或者在缺少注释的环境下。有经验的程序员在摸爬滚打中都培养出各自习惯的方式来处理各种棘手的问题。无论有哪些方法,其所具备的共同点是要细心的对待每一行代码,具备耐心和目的性,要了解当前所分析的代码实现的功能。不在这里更深入的讨论这个话题,向有感兴趣的朋友推荐一本讲述如何阅读代码的参考资料《Code Reading》,这个作者另外一本值得阅读的书是《Code Quality》,都值得阅读。

这次要分析的代码来自AndroidSnippets.org,对于经常光顾AR的朋友应该非常了解,本站对于他们的内容时刻保持着关注,除了粘贴一些例子以外,更主要的是剖析例子中所涉及的知识点,这是体现AndroidRes存在价值的方式。上边的一些话扯的稍远了点,让我们回到正题吧。

介绍本例中两个最基本的类:其中第一个是承载HandlerThreadHandler的类,另外一个是用来装载实例的容器,二者的关系可以看作为玻璃球和玻璃球罐子。

先来分析第一个类,“玻璃球”的内部除了包括HandlerThread和Handler以外,还需要创建一个默认的消息响应类,为了体现很好的拓展性,可以在声明“玻璃球”类时指定其它HandleMessage方法。下边先来看完整的源代码(保留原有英文注释):

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Handler.Callback;
import android.util.Log;  

/**
 * The transceiver provides a convenient way to decorate an instance with receive and transmit capabilities.
 * It can (and this is encouraged) be used with the {@link Broker} instance.
 * The naming thing is not part of the Transeiver's functionality, but may be useful while debugging.
 * This class can be used for subclassing or as a "has-a"-relation, as it is possible to set
 * an arbitrary {@link Callback } listener.
 *
 */
public class Transceiver {
    private String mName="Transceiver";
    private static int ID=0;  

    private HandlerThread mHandlerThread;
    private Handler mHandler;  

    // delegates the messages, when no proprietary callback
    // is associated with the transeiver
    private class HandlerListener implements Callback{   

        @Override
        public boolean handleMessage(Message msg) {
            return Transceiver.this.handleMessage(msg);
        }  

    }  

    /**
     * Creates a transceiver with an automatically generated name
     */
    public Transceiver(){
        mName = new String("Transceiver(" + ++ID + ")");
        create(new HandlerListener());
    }  

    /**
     * Creates a transceiver with arbitrary name
     * @param name
     */
    public Transceiver(String name){
        ++ID;
        mName = name;
        create(new HandlerListener() );
    }  

    /**
     * Creates a transceiver with arbitrary name and an own listener
     * @param name a custom name
     * @param l your own listener
     */
    public Transceiver(String name, Callback l){
        ++ID;
        mName = name;
        create(l);
    }  

    private void create(Callback l){
        mHandlerThread = new HandlerThread(getName());
        mHandlerThread.start();
        Log.d(mName, "Waiting for looper...");
        // we need to wait (blocking) to get the looper instance
        while(!mHandlerThread.isAlive()) {};
        mHandler = new Handler(mHandlerThread.getLooper(), l);
        Log.d(mName, "Ready");
    }  

    /**
     * @return The name of this transceiver
     */
    public final String getName() { return mName; }  

    /**
     * @return Its internal handler
     */
    public final Handler getHandler() { return mHandler; }  

    /**
     * The callback method for received messages. You can override it, when deriving from Transceiver.
     * @param msg the message
     * @return true, if the message was handled.
     */
    public boolean handleMessage(Message msg) { return false; }
}

按照物理顺序详细分析代码的作用

  • mNameID用来给线程添加一个可识别的标签;
  • mHandlerThreadmHandler两个变量分别装载当前线程中HandlerThreadHandler的实例;
  • HandlerListener类作为Callback的子类,响应Message信息的行为定义在  HandleMessage 方法中。在这个例子中实际的行为写在类中的一个独立方法中,然后再由HandleMessage去调用这个方法,这样做的目的为了让其子类可以重写基类(Transceiver)中HandleMessage的行为。
  • 三种不同的构造函数。第一个可以根据系统自动分配线程标签,第二个是接受指定命名,前两种还有一个共同点是使用类中默认的handleMessage,第三个包含两个参数,一个是标签名称,另外一个用于自定义新的handlerMessage行为。
  • create方法是创建HandlerThread的核心部分,将Callback类型参数应用于HandlerThread的Looper类中处理消息循环(接收信息并作出回应 – Looper class used to run a message loop for a thread.)。
  • getName和getHandler无需解释,一个是用于返回当前HandlerThread标签的方法,另外一个返回Handler实例。

接下来分析“玻璃球盒子”。小时候经常将手中的玻璃球随意乱放,而且经常有丢失的情况发生,就是因为没有一个统一管理的方法。当创建了多个HandlerThread之后,有必要设置一个容器将它们合理的安置,以至于当需要的时候可以快速的通过标签找到想要索引的线程。下边先来看完整的源代码(保留原有英文注释):

import java.util.ArrayList;
import android.os.Handler;
import android.os.Message;  

/**
 * The broker is a kind of repository for {@link Handler} instances. It provides a simple and very convenient mechanism to
 * establish communication between different handlers. Just subscribe a handler instance and use the returned address with
 * this Broker. This class is thread safe, of course.
 * Usage example:
 * 
 * this.MyCompAddr = Broker.instance.subscribe( new Handler() );
 * // or, when using transceiver
 * this.MyRendererAddr = Broker.instance.subscribe( new MyTransceiver("Renderer").getHandler() );
 * [...]
 * // how to send messages
 * Message m = Message.obtain();
 * m.what = 1; // could be an identifier or op-code or whatever you want
 * Bundle b= new Bundle(); // or use more complex (key,value)-pairs.
 * b.putString("data", "Any Data");
 * m.setData(b);
 * Broker.instance.post(this.MyRendererAddr, m);
 * 
 *
 */
public class Broker {  

    /**
     * The static instance.
     */
    public static Broker instance=new Broker();
    private ArrayList< Handler > mHandler;  

    private Broker()
    {
        mHandler = new ArrayList< Handler > ();
    }  

    /**
     * Subscribe a handler, so you can send messages easily to it.
     * Mind there is no check for doubled subscription.
     * @param h The handler
     * @return The "address" for the handler
     * @see {@link unsubscribe() }
     */
    public synchronized int subscribe(Handler h){
        ArrayList< Handler > hl=mHandler;
        hl.add(h);
        return hl.size()-1;
    }  

    /**
     * Unsubscribes a handler. Invalid addresses are ignored
     * @param address
     */
    public synchronized void unsubscribe(int address){
        if(isAddressValid(address))
            mHandler.remove(address);
    }  

    protected synchronized final boolean isAddressValid(int address){
        ArrayList< Handler > h=mHandler;
        return (!h.isEmpty() && address >= 0 && address < h.size() );
    }  

    /**
     * Post a message to addressed receiver.
     * @param address
     * @param m
     * @return true, if address exists, i.e. message could be delivered
     */
    public synchronized boolean post(int address, Message m){
        ArrayList< Handler > h=mHandler;
        if(!isAddressValid(address))
            return false;  

        h.get(address).sendMessage(Message.obtain(m));
        return true;  

    }  

    /**
     * Sends a message to all connected handler
     * @param m The message
     * @return The number of notified handler, e.g. send messages.
     */
    public synchronized int broadcast(Message m)    {
        ArrayList< Handler > h=mHandler;
        int n=h.size();
        for(int i=0; i < n; ++i)
        {
            h.get(i).sendMessage(Message.obtain(m));
        }
        return n;
    }
}

按照物理顺序详细分析代码的作用:

  • instance:在Broker类中创建一个静态实例,可以在其它方法中直接调用其资源。
  • mHandler:这个是用来存放Handler实例的数组,也是这个容器的核心,后边所创建的方法都针对这个数组来操作。
  • Broker(): 类内部的构造函数。它为mHandler建立一个ArrayList<Handler>数组实例。
  • subscribe():相当于数组的"Add"方法,返回值是当前所添加的Handler在mHandler数组中的“座位号”。
  • unsubscribe(): 相当于数组的"Remove"方法,传递一个“座位号”可以将它的实例从mHandler中移除。
  • isAddressValid(): 验证“座位号”的合法性,其原理仅仅是判断这个号是否超出了当前数组中子集的数量或者是否为不小于0的整数。
  • post(): 根据“座位号”将Message发送给这个指定的线程。
  • broadcast(): 可以根据这个意思理解为拿个大喇叭对当前容器内的所有线程实例广播。如果说post是k98步枪,那么这个可以算得上是MG42了。

以上就是对线程间通讯方法的简单介绍,下边是一些更加实际的例子,可以帮助大家理解如何在你的程序中调用上边的类。

例子一:

/**
* a place where I store my addresses.
* Read-only access to once set addresses is thread-safe!
*/
public class AddressPool {
    public static int Subsystem1,Subsystem2,MyActivity;
}  

// #extending Transceiver (is-a-relation) # 

public class Subsystem2 extends Transceiver {
    public final static String TAG="Subsystem2";
    public final static int WHAT = 3862; // a magic number  

    public boolean handleMessage(Message m){  

        switch(m.what){
        case Subsystem1.WHAT:
            Log.i(TAG,"Received message from " + Subsystem1.TAG);
            // attention: when answering we need a new message
            Message msg= Message.obtain(m);
            Broker.instance.post(AddressPool.Subsystem1, msg);
            break;
        case MyActivity.WHAT:
            Log.i(TAG,"Received message from " + MyActivity.TAG );
            break;
        }
        return true;
    }
}

例子二:

// #wrapping/using Transceiver (has-a-relation) # 

package com.o1.android;  

import java.util.Timer;
import java.util.TimerTask;  

import com.o1.android.util.message.Broker;
import com.o1.android.util.message.Transceiver;  

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Handler.Callback;
import android.util.Log;  

public class Subsystem1 extends TimerTask implements Callback{
    public final static String TAG="Subsystem1";
    public final static int WHAT = 175; // any identifier
    private Transceiver mTransceiver;
    private int mMyAddress=-1;
    private Timer mTimer;  

    public Subsystem1(){
        mTransceiver = new Transceiver(TAG, this);
        mTimer = new Timer("SubsystemTimer");  

    }  

    public void onStart(){
        mMyAddress = Broker.instance.subscribe(mTransceiver.getHandler());
        // we use a timer to asynchronously post messages
        mTimer.schedule(this, 0, 125);
    }  

    public void onQuit(){
        mTimer.cancel();
        Broker.instance.unsubscribe(mMyAddress);
    }  

    public final int getBrokerAddress() { return mMyAddress; }  

    @Override
    public boolean handleMessage(Message m) {
        switch(m.what){
        case Subsystem2.WHAT:
            Log.i(TAG,"Received message from " + Subsystem2.TAG);
            break;
        case MyActivity.WHAT:
            Log.i(TAG,"Received message from " + MyActivity.TAG);
            break;
        }
        // immediate response/delegate with a copy of our message
        Broker.instance.post(AddressPool.MyActivity, Message.obtain(m));
        return true;
    }  

    @Override
    public void run() {
        Message m= new Message();
        m.what=WHAT;
        Broker.instance.post(AddressPool.MyActivity, m);
    }
}

例子三:

// #connecting activities # 

public class MyActivity extends Activity implements Callback {  

     private Subsystem1 mSubsystem1;
     //... other member fields  

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        /*
        * We have different types of transceiver implementations.
        * One as "has" (subsystem1) and the other as "is"-relation.
        */
        mSubsystem1 = new Subsystem1();
        AddressPool.Subsystem2 = Broker.instance.subscribe(new Subsystem2().getHandler());
        // additionally, we can use activities (or other components with loopers)
        // mind that our activity implements Callback
        AddressPool.MyActivity = Broker.instance.subscribe(new Handler(this));
        // ... your stuff, e.g. a button that triggers messages  

    }   

    @Override
    public void onStart(){
        super.onStart();
        mSubsystem1.onStart();
        AddressPool.Subsystem1 = mSubsystem1.getBrokerAddress();
    }  

    @Override
    public void onDestroy(){
        super.onDestroy();
        mSubsystem1.onQuit();
    }  

    @Override
    public boolean handleMessage(Message msg) {
       // handle your messages here
    }

查看原文 AndroidSnippets.org

Tags: ,

Posted in Resource, Tutorial |

Related Posts

One Response to “线程间相互传递信息的方法”


Dialog工具箱 – ProgressDialog | 嘿嘿哈 October 19th, 2009 at 10:48 am

[...] 上边的例子中引用了Handler(official)作为multi-thread相互传递数据的载体,接下来为ProgressDialog实现一个可视化进度条(progressBar)的方法中依然需要调用Handler(Internal),借用这个例子大家也可以多了解Handler的用法。 [...]



Leave a Reply

You must be logged in to post a comment.