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

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 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 send
    01-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 : 114
    01-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(){
                @Override
                public void handleMessage(Message msg) {
                    // TODO Auto-generated method stub
                    super.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 send
    01-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 : 117
    01-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初始化子线程的handler

            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: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 send
    01-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 : 123
    01-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";

        @Override
        protected 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 thread
    01-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 : 1
    01-04 23:25:04.524: I/main-thread(1471): main thread name : main

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

你可能感兴趣的:(线程,android,handler,Class,looper)