简书链接:原创Looperparpare和Looperloop轻松玩转、子线程handler简单代码就可以理解。
文章字数:1201,阅读全文大约需要4分钟
前不久测试新版本内存泄露框架的时候,突然想虐一下自己,就写了个内存泄露的代码,并且还想让他泄露的时候不断弹出消息,但是不借助其他线程handler来实现,所以无聊写了个代码,发现验证是正确的,就把代码贴出来,给新手看看,也顺便把为什么可以这样做的原因发到这里。
问题列表
1、为什么主线程死循环而不卡死。
2、handler创建,子线程、使用
3、handler可以创建几个,Looper可以用几个。
4、handler的消息机制原理
5、子线程 弹出toast
6、looper.loop()阻塞了,那么之后的方法怎么才能实现调用, 那么让你在子线程搭建消息通讯你应该怎么搭建
回答
1、之所以不卡死,是因为它一直在处理消息,当没有消息的时候会进行等待,另外它也可以被其他线程唤醒。所以不会造成什么问题。
2、子线程先Looper.parpare() 实例化一个,一个线程只能有一个,
然后就可以创建了,发送的消息并不会被处理,需要开启looper.loop()
才能被处理。
3、handler可以创建多个,但是里面的looper一个线程只能拥有一个,looper只能创建一个,一般情况下不会把他给Loop.quit()的,因为无法再调用了。
4、原理就是hanlder发送的消息加入到消息队列,这个消息队列是threadlocal里面的,这个对象也是本身限制一个线程只能拥有一个,而Looper也是如此,加入的消息将会被Looper.looper()的死循环里面进行处理。
5、在handler.里面的run消息里面可以调用Loop.myLoopre().quit)来实现退出,这样就可以让looper()跳出阻塞。
主线程在应用启动的时候就开启looper了,是无法再prepare了,
子线程无法使用handler 是因为它是从自己线程取looper的,因此可以在子线程Looper.prepare一个,怎么搭建看下面的代码。
说明
网上很流行的文章就是子线程弹出toast的方法代码,但是没有下文,就是坑人的。
1 2 3 4
| Loop.prepare() Toast.makeText(activity.getApplication(),"" + Thread.currentThread()..getName()+ "线程的toast", Toast.LENGTH_SHORT).show(); } Loop.loop();
|
但是这样做了之后你怎么处理后面的逻辑?所以这是坑爹的,正确的方法是在loop()之前构建一个带线程延迟的死循环或者再开一个线程然后用这个线程的handler发送消息来实现处理。
下面的代码完美的演示了两种,让toast反复弹出来,而且这个代码是测试内存泄露的,你actiivty执行之后退出,它一直能反复弹出,直到弹出指定时间耗时才退出。
验证
下面代码演示的是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
** * 模拟泄漏并且弹出toast * @param activity */ public static void testLeak(Activity activity) { if (!BuildConfig.DEBUG) { return; } Thread thread = new Thread(new Runnable() { @Override public void run() { Activity activity1 = activity; Looper.prepare(); Handler handler = new Handler(); while (true) { try { Log.w(TAG, "继续持有对象" + activity+Thread.currentThread().getName()); Thread.sleep(1000); handler.postDelayed(new Runnable() { int showCount = 0;
@Override //反复发消息,这里就是永远死不掉的逻辑, public void run() { Toast.makeText(activity.getApplication(), activity.isDestroyed() + " isdestory? "+Thread.currentThread().getName(), Toast.LENGTH_SHORT).show(); handler.postDelayed(this, 4000);//如果loop退出了,这里也不会再执行了 if (showCount > 10) { Looper.myLooper().quit();//死掉吧。。 } showCount++;
} }, 2000); Looper looper = Looper.myLooper(); new Thread("子子线程") { @Override public void run() { String name = Thread.currentThread().getName(); handler.post(new Runnable() { @Override public void run() {//这类似在子线程中调用主线程的handler.post一样的道理,只不过这里是用的子线程的handler.来玩toast. 子线程本身已经looper.perpare了可以直接toast,但是现在和线程不行只能用叫做'子线程'的的handler进行使用 Toast.makeText(activity.getApplication(),"我是再" + name + "线程的toast", Toast.LENGTH_SHORT).show(); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Log.w(TAG, "loop quee count:" + looper.getThread().getName() + "" + ",是否当前线程:" + looper.isCurrentThread()); } } }.start(); Looper.loop();//这句话会阻塞了 主线程为什么死循环不会阻塞你就懂了,因为一直在handler里面循环,而且还有其他现成可以用主线程的handler给主线程发消息, 自身也一样可以。 Log.w(TAG, "终于退出了"); break;//loop已死,(阻塞)死循环下去没有意义了,looper.pare只能调用一次,除非反射清空死掉的looper // 。 } catch (InterruptedException e) { e.printStackTrace();
} } } }); thread.setName("子线程"); thread.start(); }
|
补充
今天我的hookui需要加一个翻译功能,由于是xposed插件,一般情况是写一个死循环 ,然后通过唤醒,如果没有订阅任务甚至直接终止死循环,又才开始轮询。
现在发现初次之外,可以用这个实现。
子线程直接new一个handler looper() 就不用管它了,把的的持有引用弄到主线程来就行了,然后主线程拿这个handler.post消息到后台线程, 这个post操作是执行的子线程功能,而非主线程。

