2Activity与子线程:Handler+Message

不会同时有2Activity处于Resume状态,系统中没有2Activity面对面交谈方法。Activity一直处于一个主UI线程中;其它如网络请求这种耗时操作则多处于子线程;还有后台的Service服务也是子线程。主UI线程总是和各种子线程共存、交互,所以Activity还会和子线程、Service等通讯传参。

 

Intent是在启动Activity的同时捎带数据,那么Activity如何向其它线程传递数据?从本节开始讲。 

 

Handler是一个消息处理器,一个工具类。如果主线程和子线程共同拥有一个Handler实例,那么这个handler既可以使用主线程资源又可以访问子线程资源达到通讯目的。UI线程默认有个Looper对象,叫做消息泵,它里面有个MessageQueue的东东,叫做消息队列。当handler发消息的时候,消息先被存放到这个消息队列里,而消息泵是循环遍历消息队列的,当它发现队列里出现了新的消息,那么就会通知UI,然后UI再使用handler读消息。也就是handler自己发消息,自己读消息,只是读和发是在不同的线程里。这就是子线程通过Handler联系主线程的机制。

千言万语一句话,谁收消息谁创建Handler并拥有自己的Looper;谁发消息就调用接收者的Handler

具体怎么存消息,怎么遍历消息队列,都由系统完成。只关注子线程如何创建消息封装数据然后发送、主线程如何读消息进行处理即可。

 

怎么发消息:

// 1.创建消息体

Message msg = new Message();       // 直接创建一个消息体

msg = handler.obtainMessage();    // 享元模式,从消息体池获取可用的消息体

    

// 2.封装数据

Bundle data = new Bundle();

msg.arg1 = 1;            // 简单的int类型数值,可使用这2个属性存储

msg.arg2 = 2;

msg.what = 0;            // 消息标识码,也是个int数值

msg.obj = new Object();    // NB,存储任意类型的数据

msg.setData(data);        // 存储Bundle封装的数据

    

// 3.发送消息

handler.sendMessage(msg);

 

怎么读消息(示例代码):

if(msg.what == 0){

    int a1 = msg.arg1;

    int a2 = msg.arg2;

    Object obj = msg.obj;

    Bundle data = msg.getData();

    //data.getString("");

    

    Log.e("ard""从消息标识码来看,很好");

}

 

线程间的通讯有以下3种情况:

1)子线程 -> 主线程

这个图就是上面讲过的子线程向主线程发消息的机制。只要知道黑色方框里如何操作handler就很完美了。

 

UI线程代码:

public class MainActivity extends Activity {

    /** 属于主UI的消息处理器,子线程向此发消息 **/

    private Handler handler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            if (msg.what == 99) {

                Log.e("ard"msg.obj.toString();

            }

        }

    };

}    

 

子线程代码:

/** 子线程,测试向主UI发消息 **/

public class ChildThread1 extends Thread {

    

    private Handler handler;

    /** 子线程,测试向主UI发消息。参数:UI线程中创建的handler **/

    public ChildThread1(Handler handler) {

        this.handler = handler;

    }

    @Override

    public void run() {

        super.run();

        

        Message msg = handler.obtainMessage();

        msg.what = 99;

        msg.obj = "这里是子线程1";

        

        handler.sendMessage(msg);

    }

}

 

当运行子线程时,把UI线程的Handler实例传入即可:

    // 子线程向主线程通讯

    new ChildThread1(handler).start();

 

2)主线程 -> 子线程

主线程向子线程发消息的时候很少,但有这种情况也看一下。记住关键一点:子线程要有自己的Looper

 

子线程代码:

/**

 * 子线程测试类

 * @author cuiweiyou.com

 */

public class ChildThread2 extends Thread {

    private Handler handler;

    /** 获取子线程的消息处理器 **/

    public Handler getHandler(){

        handler = new Handler(){

            @Override

            public void handleMessage(Message msg) {

                super.handleMessage(msg);

                

                if(msg.what == 112){

                    Log.e("ard""主线程发来的消息");

                }

            }

        };

        

        return handler;

    }

    @Override

    public void run() {

        super.run();

        

        // 非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()创建启用Looper

        Looper.prepare();

        

        // 开始工作,从消息队列里取消息,处理消息

        Looper.loop();

        

        // 写在Looper.loop()之后的代码不会被执行,函数内部是一个 for (;;) 循环

        // 当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行

    }

}

 

主线程代码:

    // 主线程向外部类的子线程通讯

    ChildThread2 thread2 = new ChildThread2();

    handler2 = thread2.getHandler();

    thread2.start();

    Message msg = handler2.obtainMessage();

    msg.what = 112;

    handler2.sendMessage(msg);

 

上面的子线程和主线程不在同一个java文件中,是外部类。还有种情况,子线程是主UI的一个内部类,尽管如此,主线程向其发消息时还是如图所示,只在代码上有所简化。上个全貌吧。

public class MainActivity extends Activity {

    /** 在子线程内部类里实例化的消息处理器,测试主UI向子线程发消息 **/

    private Handler handler3;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        new InnerThread().start();    // 在UI创建时(onCreate)启动子线程,否则NullPointException

    }

// 通过事件触发,如点击按钮

    public void onTest(View v) {

        Message msg1 = handler3.obtainMessage();

        msg1.what = 100;

        handler3.sendMessage(msg1);

    }

    /** 内部类,一个子线程 **/

    class InnerThread extends Thread {

        @Override

        public void run() {

            super.run();

            // 子线程仍然须要有自己的消息泵

            Looper.prepare();

            

            // 主线程声明,子线程初始化

            handler3 = new Handler() {

                @Override

                public void handleMessage(Message msg) {

                    super.handleMessage(msg);

                    if (msg.what == 100) {

                        Log.e("ard""主线程->内部类消息");

                    }

                }

            };

            Looper.loop();

        }

    }

}

 

3)子线程 -> 子线程

这种情况更少,其实明白了上面两种情况,这里就不必扯太多。这里就还是使用内部类给个示例:

public class MainActivity extends Activity {

    private Handler handler3;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        new InnerThread().start();

    }

    public void onTest(View v) {

        new Thread() {

            public void run() {

                Message msg = handler3.obtainMessage();

                msg.what = 33;

                handler3.sendMessage(msg);

            }

        }.start();

    }

    class InnerThread extends Thread {

        @Override

        public void run() {

            super.run();

            Looper.prepare();

            handler3 = new Handler() {

                @Override

                public void handleMessage(Message msg) {

                    super.handleMessage(msg);

                    if (msg.what == 33) {

                        Toast.makeText(MainActivity.this"内部子线程->内部子线程消息", 0).show();

                    }

                }

            };

            Looper.loop();

        }

    }

}

 

3Activity与子线程:回调

回调是非常好用的方式,经常用在Activity和子线程通讯中。具体用法是,在创建子线程的时候,把Activity内拥有的一个对象传过去,子线程中触发某事件后,调用这个对象的方法;而这个对象实际在Activity里,索引对象执行方法时可以对Activity中的其它资源进行操作。

回调通常是采用面向接口编程的方式使用,所以代码结构有些繁琐。实际上一张图搞定:

 

4ActivityServiceMessenger+Handler

上面的Handler+Message是线程之间的通讯,Messenger+Handler是进程间的通讯。什么是线程?什么是进程?打开Windows系统的资源管理器,看到里面一行一行的,就是进程,基本上是一个程序对应一个进程,也有一个程序对应多个进程。

 

线程则是进程内执行某项功能的单元。比如一个播放器,既要显示播放界面,又要调用硬件播放声音,这两个功能单元就是2个线程,同时进行着,同属于播放器进程。

进程和线程的具体概念使用搜索引擎找面试题即可,这里就不讲理论了。

 

Android里也是如此。一般情况下一个应用对应一个进程,其中包括UI主线程;同时多数情况下,Service运行在独立的进程中(startService方法启动)。

startActivity启动的新Aty和执行这个方法的Aty默认在同一进程。要想使它们不再同一进程,即新Aty在另一个进程,可以配置AndroidManifest文件中<activity>标签中的android:process属性,指定一个字符串作为新的进程名称。Activity配置此属性,就会运行在自己的进程中。如果android:process属性的值以":"开头,则表示这个进程是私有的;如果android:process属性的值以小写字母开头,则表示这是一个全局进程,允许其它应用程序组件也在这个进程中运行。必须保证process属性字符串内至少有一个"."字符。process属性默认是应用程序的package名。

在不同进程的Activity,不会改变它们所在的task栈。

 

扯远了。Messenger+Handler主要还是应用在ActivityService之间通讯。这里埋个橛子,到Service章节再挖吧。

 

5ActivityService、跨应用:广播

Broadcast,意思为广播,broadcast receiver 意为收音机。和现实生活一样,无线电广播是面向大众发布的,但是有收音机的人才能收听到。系统(系统级app)能发广播,应用app当然也可以发广播;广播是面向整个系统的,如果有个收音机正好在Activity内部,那么就能按照频道接收到其它app广播出来的信息了。若是在本应用内某进程或线程发的广播,即应用内Activity和子线程或Service进行广播通讯。

尽管Broadcast Receiver一般定义在Activity内部,但BroadcastActivity不是同一个进程。所以,可以使用Broadcast实现ActivityActivity直接的信息传递,尽管只有一个为Resume状态。

 

示例一个广播最简单流程,同时也是2Activity通过广播通讯。

1MainActivity中注册收音机

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    

    // 1.定义收音机

    broadcastReceiver = new BroadcastReceiver() {

        

        // 4.收到广播

        @Override

        public void onReceive(Context context, Intent intent) {

            Log.e("ard""广播发回" + intent.getStringExtra("kkk"));

            ((TextView)findViewById(R.id.tvma)).setText("广播改变文本");

        }

    };

    

    IntentFilter ifer = new IntentFilter();

    ifer.addAction("helloAty1");    // 收听哪个频道

    

    // 2.注册动态收音机

    registerReceiver(broadcastReceiverifer);

}

// 响应按钮事件。跳转界面

public void go2(View v){

    Intent i = new Intent(this, Activity2.class);

    startActivity(i);

}

@Override

protected void onDestroy() {

    super.onDestroy();

    

    // 解除收音机。应用退出时registerReceiver注册的动态收音机须解除

    unregisterReceiver(broadcastReceiver);

}

 

2Activity2中发广播

// 响应按钮事件。在本界面退出时执行亦可

public void helloAty1(View v){

    Intent i = new Intent();

    i.putExtra("kkk""vvv");

    i.setAction("helloAty1");    // 发送频道

    

    sendBroadcast(i);    // 3.广播

}

 

比较简单,就不配图了。

 

此处示例是动态广播用法,另外还有静态广播。后文Broadcast章节还有讲解。

 

6)跨应用:AIDL

Android Interface Definition Language,安卓接口定义语言,用于跨进程(或不同应用的进程)间通讯。具体还是某应用的Activity和另一个应用的Service,因为Android里只有一个应用的Activity处于Resume活动状态,而其它应用的Activity处于PauseStop状态,只有Service这一进程可以隐隐的活跃着。

 

因此,还是在Service章节详述。