java.lang.AssertionError: use looper thread, must call Looper.prepare() first!
在消息处理中必须先调用Looper类的prepare()方法。
如下两段示例代码:一个是MainActivity,一个是由其开启的Activity。系统默认是给它创建了消息队列,而ActivityTwo由MainActivity创建和开启,公用MainActivity中的消息队列,因此不需要显式的调用Looper的两个方法来处理子线程的消息。
public class MainActivity extends Activity implements OnClickListener{
public Button bt_click;
public Button bt_click_2;
public Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_click = (Button) findViewById(R.id.bt_click);
bt_click_2 = (Button) findViewById(R.id.bt_click_2);
bt_click.setOnClickListener(this);
bt_click_2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_click:
// 意图开启的Activity任然是共用了默认的消息队列
Intent intent = new Intent(MainActivity.this, ActivityTwo.class);
startActivity(intent);
break;
case R.id.bt_click_2:
// 开启新的线程,且该线程使用了Handler,但是已经不是一个线程了,需要另外调用Looper
AnotherClass.StartThread(MainActivity.this);
break;
default:
break;
}
}
}
public class ActivityTwo extends Activity implements OnClickListener{
public Button bt_click;
public Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_two);
bt_click = (Button) findViewById(R.id.bt_click);
/*
* 此处的Handler就公用了MainActivity中默认的消息队列,所以不必显示的调用Looper的方法
*/
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Toast.makeText(ActivityTwo.this, "ActivityTwo: 消息 1", 0).show();
break;
}
}
};
bt_click.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_click:
// 子线程发消息
new Thread(){
public void run() {
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
};
}.start();
break;
default:
break;
}
}
}
而如果在ActivityTwo或者MainActivity中调用了另外一个类(非组件类),而该类中涉及到了子线程消息更新,那么
由于不能共享系统默认创建的消息队列,所以就只能自己显式的调用Looper的两个方法以处理子线程的消息
以更新UI(如弹出土司)
例如下面的例子,在MainActivity中通过点击事件调用其中的StartThread()方法,开启线程,显然该线程已经和MainActivity中的线程不同,不能共用该消息队列,所以此处需要在new Thread的run()方法中调用Looper新建消息队列。如果在new Thread()之前调用,也就是线程还没开启的时候,所有的代码还算是MainActivity线程空间中的,那么将会报出“统一线程中只能新建一个消息队列”的异常
java.lang.RuntimeException: Only one Looper may be created per thread
public class AnotherClass {
public static Handler handler;
public static void StartThread(final Context context){
new Thread(){
public void run() {
// 需要run了之后才可以调用
// 在新线程中使用Looper包裹Handler是经典用法,见源码或者文档
Looper.prepare();
handler = new Handler(){
// do sth
};
Toast.makeText(context, "AnotherClass : test message queue", 0).show();
Looper.loop();
};
}.start();
}
}
Looper和Handler是天生的一对,只是系统会默认创建一个消息队列,导致在Activity或者其他组件中使用Handler的时候不必去自己调用Looper,而在其他线程中,如果想要使用Looper和Handler机制,就必须用一个Looper 的prepare和loop方法将Handler包裹起来。
Looper和Handler的另外一个问题
现在主线程开启总需要调用其他类的方法,而其他类的方法是耗时方法因此在子线程中完成。然而调用者需要知道这个第三方的类是什么时候完成了这个耗时任务,以决定下一步的操作。一开始觉得应该使用内容观察者或者广播的形式进行全局的通知,告诉调用者任务做完了。但是感觉这样太重量级了,杀鸡用牛刀的感觉。一直想要使用一种进/线程通信的方式,毕竟这样更加简单一些。没有考虑过使用Handler和Looper机制,应为**觉得**Handler和Looper属于是一对儿的,调用者和执行者在不同的线程中执行,就算发了消息也未必可以发到指定的消息队列中,也就是说被调用者发消息不一定可以发到调用者的消息循环队列中。今天测试了一下,只要调用者给被调用者一个Handler对象,被调用者的子线程使用这个Handler就可以给调用者的线程发送消息,而且进入了调用者线程的消息队列。
示例代码分析
如果在子线程中使用handler = new Handler(){ handleMessage(){};}; 方法,那么必须先调用Looper.prepare()方法,即使没有重写handleMessage()方法,只要是在子线程中使用handler = new Handler();初始化语句就必须先调用Looper.prepare()
而如果是在主线程中初始化或者赋值handler,那么就不需要使用Looper。
public class ChildThread {
protected static final String TAG = "child-thread";
private Handler handler; // = new Hand