消息循环与Looper类的应用

了解Looper

Looper是用于给一个线程添加一个消息队列(MessageQueue),并且循环等待,当有消息时会唤起线程来处理消息的一个工具,直到线程结束为止。通常情况下不会用到Looper,因为对于Activity,Service等系统组件,Frameworks已经为我们初始化好了线程(俗称的UI线程或主线程),在其内含有一个Looper,和由Looper创建的消息队列,所以主线程会一直运行,处理用户事件,直到某些事件(BACK)退出。

如果,我们需要新建一个线程,并且这个线程要能够循环处理其他线程发来的消息事件,或者需要长期与其他线程进行复杂的交互,这时就需要用到Looper来给线程建立消息队列。

使用Looper也非常的简单,它的方法比较少,最主要的有四个:

public static prepare();

public static myLooper();

public static loop();

public void quit();

使用方法如下:

  1. 在每个线程的run()方法中的最开始调用Looper.prepare(),这是为线程初始化消息队列。

  2. 之后调用Looper.myLooper()获取此Looper对象的引用。这不是必须的,但是如果你需要保存Looper对象的话,一定要在prepare()之后,否则调用在此对象上的方法不一定有效果,如looper.quit()就不会退出。

  3. 在run()方法中添加Handler来处理消息

  4. 添加Looper.loop()调用,这是让线程的消息队列开始运行,可以接收消息了。

  5. 在想要退出消息循环时,调用Looper.quit()注意,这个方法是要在对象上面调用,很明显,用对象的意思就是要退出具体哪个Looper。如果run()中无其他操作,线程也将终止运行。

Looper实例

public class LooperDemoActivity extends Activity {  
    private WorkerThread mWorkerThread;  
    private TextView mStatusLine;  
    private Handler mMainHandler;  
      
    @Override  
    public void onCreate(Bundle icicle) {  
        super.onCreate(icicle);  
        setContentView(R.layout.looper_demo_activity);  
        mMainHandler = new Handler() {  
            @Override  
            public void handleMessage(Message msg) {  
            String text = (String) msg.obj;  
            if (TextUtils.isEmpty(text)) {  
                return;  
            }  
            mStatusLine.setText(text);  
            }  
        };  
          
        mWorkerThread = new WorkerThread();  
        final Button action = (Button) findViewById(R.id.looper_demo_action);  
        action.setOnClickListener(new View.OnClickListener() {  
            public void onClick(View v) {  
            mWorkerThread.executeTask("please do me a favor");  
            }  
        });  
        final Button end = (Button) findViewById(R.id.looper_demo_quit);  
        end.setOnClickListener(new View.OnClickListener() {  
            public void onClick(View v) {  
                mWorkerThread.exit();  
            }  
        });  
        mStatusLine = (TextView) findViewById(R.id.looper_demo_displayer);  
        mStatusLine.setText("Press 'do me a favor' to execute a task, press 'end of service' to stop looper thread");  
    }  
      
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        mWorkerThread.exit();  
        mWorkerThread = null;  
    }  
      
    private class WorkerThread extends Thread {  
        protected static final String TAG = "WorkerThread";  
        private Handler mHandler;  
        private Looper mLooper;  
          
        public WorkerThread() {  
            start();  
        }  
          
        public void run() {  
            
            Looper.prepare();  
            
            mLooper = Looper.myLooper();  
            mHandler = new Handler(mLooper) {  
                @Override  
                public void handleMessage(Message msg) {  
                    StringBuilder sb = new StringBuilder();  
                    sb.append("it is my please to serve you, please be patient to wait!\n");  
                    Log.e(TAG, "workerthread, it is my please to serve you, please be patient to wait!");  
    
                    for (int i = 1; i < 100; i++) {  
                        sb.append(".");  
                        Message newMsg = Message.obtain();  
                        newMsg.obj = sb.toString();  
                        mMainHandler.sendMessage(newMsg);  
                        Log.e(TAG, "workthread, working" + sb.toString());  
                        SystemClock.sleep(100);  
                    }  
    
                    Log.e(TAG, "workerthread, your work is done.");  
                    sb.append("\nyour work is done");  
                    Message newMsg = Message.obtain();  
                    newMsg.obj = sb.toString();  
                    mMainHandler.sendMessage(newMsg);  
                }  
            };  
            Looper.loop();  
        }  
          
        public void exit() {  
            if (mLooper != null) {  
            mLooper.quit();  
            mLooper = null;  
            }  
        }  
          
       
        public void executeTask(String text) {  
            if (mLooper == null || mHandler == null) {  
                Message msg = Message.obtain();  
                msg.obj = "Sorry man, it is out of service";  
                mMainHandler.sendMessage(msg);  
                return;  
            }  
            Message msg = Message.obtain();  
            msg.obj = text;  
            mHandler.sendMessage(msg);  
        }  
    }  
} 

这个实例中,主线程中执行任务仅是给服务线程发一个消息同时把相关数据传过去,数据会打包成消息对象(Message),然后放到服务线程的消息队列中,主线程的调用返回,此过程很快,所以不会阻塞主线程。服务线程每当有消息进入消息队列后就会被唤醒从队列中取出消息,然后执行任务。服务线程可以接收任意数量的任务,也即主线程可以不停的发送消息给服务线程,这些消息都会被放进消息队列中,服务线程会一个接着一个的执行它们----直到所有的任务都完成(消息队列为空,已无其他消息),服务线程会再次进入休眠状态----直到有新的消息到来。

如果想要终止服务线程,在mLooper对象上调用quit(),就会退出消息循环,因为线程无其他操作,所以整个线程也会终止。

需要注意的是当一个线程的消息循环已经退出后,不能再给其发送消息,否则会有异常抛出"RuntimeException: Handler{4051e4a0} sending message to a Handler on a dead thread"。所以,建议在Looper.prepare()后,调用Looper.myLooper()来获取对此Looper的引用,一来是用于终止(quit()必须在对象上面调用); 另外就是用于接收消息时检查消息循环是否已经退出(如上例)。

从上面的分析过程可知,消息循环的核心是Looper,Looper持有消息队列MessageQueue对象,一个线程可以把Looper设为该线程的局部变量,这就相当于这个线程建立了一个对应的消息队列。Handler的作用就是封装发送消息和处理消息的过程,让其他线程只需要操作Handler就可以发消息给创建Handler的线程。由此可以知道,在上一篇《Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面》中,UI线程在创建的时候就建立了消息循环(在ActivityThread的public static final void main(String[] args)方法中实现),因此我们可以在其他线程给UI线程的handler发送消息,达到更新UI的目的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容