不会同时有2个Activity处于Resume状态,系统中没有2个Activity面对面交谈方法。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种情况:
这个图就是上面讲过的子线程向主线程发消息的机制。只要知道黑色方框里如何操作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();
主线程向子线程发消息的时候很少,但有这种情况也看一下。记住关键一点:子线程要有自己的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();
}
}
}
这种情况更少,其实明白了上面两种情况,这里就不必扯太多。这里就还是使用内部类给个示例:
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();
}
}
}
回调是非常好用的方式,经常用在Activity和子线程通讯中。具体用法是,在创建子线程的时候,把Activity内拥有的一个对象传过去,子线程中触发某事件后,调用这个对象的方法;而这个对象实际在Activity里,索引对象执行方法时可以对Activity中的其它资源进行操作。
回调通常是采用面向接口编程的方式使用,所以代码结构有些繁琐。实际上一张图搞定:
上面的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主要还是应用在Activity和Service之间通讯。这里埋个橛子,到Service章节再挖吧。
Broadcast,意思为广播,broadcast receiver 意为收音机。和现实生活一样,无线电广播是面向大众发布的,但是有收音机的人才能收听到。系统(系统级app)能发广播,应用app当然也可以发广播;广播是面向整个系统的,如果有个收音机正好在Activity内部,那么就能按照频道接收到其它app广播出来的信息了。若是在本应用内某进程或线程发的广播,即应用内Activity和子线程或Service进行广播通讯。
尽管Broadcast Receiver一般定义在Activity内部,但Broadcast和Activity不是同一个进程。所以,可以使用Broadcast实现Activity与Activity直接的信息传递,尽管只有一个为Resume状态。
示例一个广播最简单流程,同时也是2个Activity通过广播通讯。
(1)MainActivity中注册收音机:
@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(broadcastReceiver, ifer);
}
// 响应按钮事件。跳转界面
public void go2(View v){
Intent i = new Intent(this, Activity2.class);
startActivity(i);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 解除收音机。应用退出时registerReceiver注册的动态收音机须解除
unregisterReceiver(broadcastReceiver);
}
(2)Activity2中发广播:
// 响应按钮事件。在本界面退出时执行亦可
public void helloAty1(View v){
Intent i = new Intent();
i.putExtra("kkk", "vvv");
i.setAction("helloAty1"); // 发送频道
sendBroadcast(i); // 3.发广播
}
比较简单,就不配图了。
此处示例是动态广播用法,另外还有静态广播。后文Broadcast章节还有讲解。
Android Interface Definition Language,安卓接口定义语言,用于跨进程(或不同应用的进程)间通讯。具体还是某应用的Activity和另一个应用的Service,因为Android里只有一个应用的Activity处于Resume活动状态,而其它应用的Activity处于Pause或Stop状态,只有Service这一进程可以隐隐的活跃着。
因此,还是在Service章节详述。