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;@Overrideprotected 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);}@Overridepublic 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,但是已经不是一个线程了,需要另外调用LooperAnotherClass.StartThread(MainActivity.this);break;default:break;}}
}public class ActivityTwo extends Activity implements OnClickListener{public Button bt_click;public Handler handler;@Overrideprotected 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(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:Toast.makeText(ActivityTwo.this, "ActivityTwo: 消息 1", 0).show();break;}}};bt_click.setOnClickListener(this);}@Overridepublic 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 Handler();public ChildThread(Handler handler) {super();this.handler = new Handler();Log.i(TAG, "befor assign : " + this.handler.toString());}public void doSth(){new Thread(){public void run() {try {//Looper.prepare();sleep(2000);handler.sendEmptyMessage(200);Log.i(TAG, "AFTER assign : " + handler.toString());Log.i(TAG, "msg send");Log.i(TAG, "handler in child thread : " + handler.toString());Log.i(TAG, "child thread ID : " + Thread.currentThread().getId());Log.i(TAG, "child thread name : " + Thread.currentThread().getName());//Looper.loop();} catch (InterruptedException e) {Log.e(TAG, "InterruptedException in child thread " + e.toString());e.printStackTrace();}};}.start();}}

对应的日志输出如下:
此时的子线程的Handler由于没有重写handleMessage()方法,所以属于是系统handler

android.os.Handler

01-04 23:13:48.771: I/child-thread(1356): befor assign : Handler (android.os.Handler) {4176b1d8}01-04 23:13:50.868: I/child-thread(1356): AFTER assign : Handler (android.os.Handler) {4176b1d8}01-04 23:13:50.868: I/child-thread(1356): msg send01-04 23:13:50.871: I/child-thread(1356): handler in child thread : Handler (android.os.Handler) {4176b1d8}01-04 23:13:50.871: I/child-thread(1356): child thread ID : 11401-04 23:13:50.871: I/child-thread(1356): child thread name : Thread-114

若在初始化Handler的时候重写其handleMessage()方法,Handler就会改变,和具体所在工程相关

代码如下:

public class ChildThread {protected static final String TAG = "child-thread";private Handler handler; // = new Handler();public ChildThread(Handler handler) {super();this.handler = new Handler();Log.i(TAG, "befor assign : " + this.handler.toString());this.handler = new Handler(){@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubsuper.handleMessage(msg);}};Log.i(TAG, "AFTER assign : " + this.handler.toString());}public void doSth(){new Thread(){public void run() {try {//Looper.prepare();sleep(2000);handler.sendEmptyMessage(200);Log.i(TAG, "msg send");Log.i(TAG, "handler in child thread : " + handler.toString());Log.i(TAG, "child thread ID : " + Thread.currentThread().getId());Log.i(TAG, "child thread name : " + Thread.currentThread().getName());//Looper.loop();} catch (InterruptedException e) {Log.e(TAG, "InterruptedException in child thread " + e.toString());e.printStackTrace();}};}.start();}}

日志输出如下:

01-04 23:21:44.381: I/child-thread(1405): befor assign : Handler (android.os.Handler) {4176bff0}01-04 23:21:44.422: I/child-thread(1405): AFTER assign : Handler (com.example.msgdemo.ChildThread$1) {4176c740}01-04 23:21:46.520: I/child-thread(1405): msg send01-04 23:21:46.520: I/child-thread(1405): handler in child thread : Handler (com.example.msgdemo.ChildThread$1) {4176c740}01-04 23:21:46.521: I/child-thread(1405): child thread ID : 11701-04 23:21:46.521: I/child-thread(1405): child thread name : Thread-117

而如果使用调用者传递过来的Handler,那么就可以向调用者的消息队列中发送消息了,而且调用者的Looper也可以从消息队列中取出消息进行处理。对于简单的进/线程之间的同步来说,这种方式很好用。

子线程代码:

public class ChildThread {protected static final String TAG = "child-thread";private Handler handler; // = new Handler();public ChildThread(Handler handler) {super();this.handler = new Handler();Log.i(TAG, "befor assign : " + this.handler.toString());this.handler = handler;// 使用new 出来的handler是没办法把消息发送到调用者的消息队列中;// 必须使用调用者传递过来的handler初始化子线程的handlerLog.i(TAG, "AFTER assign : " + this.handler.toString());}public void doSth(){new Thread(){public void run() {try {//Looper.prepare();sleep(2000);handler.sendEmptyMessage(200);Log.i(TAG, "msg send");Log.i(TAG, "handler in child thread : " + handler.toString());Log.i(TAG, "child thread ID : " + Thread.currentThread().getId());Log.i(TAG, "child thread name : " + Thread.currentThread().getName());//Looper.loop();} catch (InterruptedException e) {Log.e(TAG, "InterruptedException in child thread " + e.toString());e.printStackTrace();}};}.start();}}

子线程日志:

01-04 23:25:02.481: I/child-thread(1471): befor assign : Handler (android.os.Handler) {4176db78}01-04 23:25:02.491: I/child-thread(1471): AFTER assign : Handler (com.example.msgdemo.MainActivity$1) {4174fe30}01-04 23:25:04.524: I/child-thread(1471): msg send01-04 23:25:04.524: I/child-thread(1471): handler in child thread : Handler (com.example.msgdemo.MainActivity$1) {4174fe30}01-04 23:25:04.532: I/child-thread(1471): child thread ID : 12301-04 23:25:04.532: I/child-thread(1471): child thread name : Thread-123

主线程代码:

public class MainActivity extends Activity {protected static final String TAG = "main-thread";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ChildThread ct = new ChildThread(mHandler);ct.doSth();}private Handler mHandler = new Handler(){public void handleMessage(android.os.Message msg) {switch (msg.what) {case 200:Log.i(TAG, "get 200 from child thread");Log.i(TAG, "mHandler in main thread : " + mHandler.toString());Log.i(TAG, "main thread ID : " + Thread.currentThread().getId());Log.i(TAG, "main thread name : " + Thread.currentThread().getName());break;default:break;}};};}

主线程日志

01-04 23:25:04.524: I/main-thread(1471): get 200 from child thread01-04 23:25:04.524: I/main-thread(1471): mHandler in main thread : Handler (com.example.msgdemo.MainActivity$1) {4174fe30}01-04 23:25:04.524: I/main-thread(1471): main thread ID : 101-04 23:25:04.524: I/main-thread(1471): main thread name : main

对比日志可以看出来虽然不在同一个线程运行,但是子线程和主线程中的mHandler指向的是同一个Handler对象,而子线程使用这个Handler对象也确实向主线程成功的发送了消息而且主线程接收并处理了这个消息。

【Android】java.lang.AssertionError use looper thread, must call Looper.prepare() first!异常分析相关推荐

  1. java.lang.AssertionError: annotationType(): unrecognized Attribute name MODULE

    java.lang.AssertionError: annotationType(): unrecognized Attribute name MODULE 和 javax.xml.bind.JAXB ...

  2. android java.lang.IllegalArgumentException: Comparison method violates its general contract! 问题

    android  java.lang.IllegalArgumentException: Comparison method violates its general contract! 问题 jav ...

  3. android java.lang.IllegalArgumentException: The observer is null.异常解决

    android java.lang.IllegalArgumentException: The observer is null.异常解决 参考文章: (1)android java.lang.Ill ...

  4. [Android Pro] java.lang.IllegalStateException: Fragment(XXFragment) not attached to Activity异常

    [Android Pro] java.lang.IllegalStateException: Fragment(XXFragment) not attached to Activity异常 参考文章: ...

  5. java.lang.AssertionError: Activity needs to be set if initial lifecycle state is resumed

    报错:java.lang.AssertionError: Activity needs to be set if initial lifecycle state is resumed 把:    se ...

  6. java.lang.exception_如何解決java.lang.AssertionError:期望的異常:Mockito中的java.lang.Exception...

    我有一個使用spring驗證器的用戶驗證器的書面單元測試用例.下面是類.我在Spring驗證器的validate方法中面臨問題,調用服務來檢查用戶是否爲admin.但是管理服務會引發異常.所以我用tr ...

  7. java.lang.ClassCastException: com.sun.proxy.$Proxy2 cannot be cast to...异常

    java.lang.ClassCastException: com.sun.proxy.$Proxy2 cannot be cast to...异常 参考文章: (1)java.lang.ClassC ...

  8. Android --- java.lang.RuntimeException: Can‘t create handler inside thread that has not called Loop

    报错信息如下: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.pr ...

  9. android优雅私有方法注释,带有注释参数的私有方法的Android java.lang.VerifyError

    我有一个非常简单的项目可以编译,但是无法在Emulator上启动.问题在于这种方法: private void bar(@Some String a) {} // java.lang.VerifyEr ...

最新文章

  1. Python程序设计题解【蓝桥杯官网题库】 DAY13-算法训练
  2. python中类方法与实例方法的区别-python中类方法、类实例方法、静态方法的使用与区别...
  3. opporeno3详细参数_vivox30和opporeno3哪个好 vivox30和opporeno3对比评测
  4. 033_字符集和编码
  5. STM32 基础系列教程 5 – 系统定时器
  6. 今天遭遇了同样的坑,折腾了一上午
  7. html把div分成两栏,div+css制作上中下,中间两列的全屏自适应布局
  8. Linux系统rootpassword改动
  9. day05 selenium库的基础使用
  10. Spring笔记——数据源配置
  11. JSP九大内置对象(转载)
  12. mysql windows ad_mysql windows安装
  13. if/else if多分支语句(JS)
  14. git提交过滤target文件 idea_详解如何在IntelliJ IDEA中使用.ignore插件忽略不必要提交的文件...
  15. 2020年互联网大厂中秋礼盒PK!看看你的礼盒怎么样
  16. 模拟退huo算法的特点_模拟退火算法(有完整实例源代码)
  17. 史上最牛的Linux视频教程—兄弟连 学习笔记1
  18. [C#] DataView用法
  19. 一文搞定BP神经网络——从原理到应用(原理篇)
  20. 计算机亮度快捷键,电脑亮度怎么调(一个快捷键就可以设置亮度了)

热门文章

  1. ioca0中断 pic单片机_关于PIC单片机的一些经验总结 -单片机-电子工程世界网
  2. 对指针变量取地址_C语言指针简介(amp;和*运算符)
  3. 动态数组怎么定义_Excel VBA 数组基础知识,初学者不可不学的关键知识
  4. clientdataset新增append新增多条记录的时候报错 key valation
  5. python数据结构与算法(11)
  6. php 商场收银收费系统,使用的策略模式
  7. Go学习之-用vscode写go代码遇到的问题
  8. ubuntu 命令整合1
  9. Nginx(九)-- Nginx实际使用配置
  10. CSS sprites