由Android Toast 到 ThreadLocal的思考
来源:互联网 发布:js页面载入事件 编辑:程序博客网 时间:2024/04/30 23:55
由Android Toast 到 ThreadLocal的思考
在Android开发中又是我们试图这样调用代码:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread thread = new Thread(){ @Override public void run() { /** * 这里执行一些耗时的操作,然后拿到最终的结果数据 * ...$@%%#@@*(*&... */ Toast.makeText(getApplicationContext(), "加载数据成功!", Toast.LENGTH_SHORT).show(); } }; thread.start(); }}
结果崩溃了,然后打印出这样的log
我去我显示一个toast跟handler有毛线关系啊(哎,还是太年轻啊),看看Toast源码,发现有这样的代码被调用
final Handler mHandler = new Handler();
那又怎样,怎么有这样的代码被调用有为啥会崩溃呢(一脸懵逼的样子),很多人可能就知道,不过我还得说说。再看看Handler里面的源码
(哼着小曲:我尋尋覓覓尋尋覓覓 一個溫暖的懷抱 這樣的要求算不算太高...)public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } //这里是报错的关键 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async;}
唱着小曲我找到了问题的关键(看来以后还得多多哼小蛐蛐),在上面代码的12行到16行,对比一下上面出现的错误(没错就是他,就是他吻了我,呜呜….),
mLooper = Looper.myLooper();if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()");}
可以看到调用Looper.myLooper()
返回一个 null的时候就报错了(这又是为啥呢,难道我长得好看,就可以随意的让被人亲吗… ),然后再进去看看里面的代码实现
(哼着小曲:我尋尋覓覓尋尋覓覓 一個溫暖的懷抱 這樣的要求算不算太高…)
/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */public static @Nullable Looper myLooper() { return sThreadLocal.get();}
这里调用了sThreadLocal.get();
既然有get那么理应有个set才能服众啊,果然发现了在,Looper里面发现了这个set
/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */public static void prepare() { prepare(true);}private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));}
共有方法prepare()
里面调用了静态私有的prepare(boolean quitAllowed)
方法,看来我们找到解决方法了,那就是调用一下Looper.prepare()
方法(恩恩, 我终于可以打那个亲我的那个男孩了,嘻嘻)
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread thread = new Thread(){ @Override public void run() { Looper.prepare(); /** * 这里执行一些耗时的操作,然后拿到最终的结果数据 * ...$@%%#@@*(*&... */ Toast.makeText(getApplicationContext(), "加载数据成功!", Toast.LENGTH_SHORT).show(); Looper.loop(); } }; thread.start(); }}
(报仇的感觉真爽,下次我还想让他亲我,然后我再打他,哈哈)不过这里,第16行添加了一行Looper.loop();
的代码,这里稍后解释。
整个问题的产生源于Toast里面用到了Handler,而Handler用到了Looper,那为啥Toast要使用到Handler,可以看到Toast源码里面一个内部类NT(进程回调类)使用到了
/** * schedule handleShow into the right thread */@Overridepublic void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow);}/** * schedule handleHide into the right thread */@Overridepublic void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide);}
可知这里只是使用到了Handler的消息循环的机制(自己发送消息,然后自己处理消息)。当然这里就需要使用到Looper,所以需要调用Looper.prepare()
(为当前线程设置一个Looper对象)以及调用Looper.loop()
方法(使得整个消息可以循环起来)。假设我们这样调用
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread thread = new Thread(){ @Override public void run() { Looper.prepare(); Looper.prepare(); /** * 这里执行一些耗时的操作,然后拿到最终的结果数据 * ...$@%%#@@*(*&... */ Toast.makeText(getApplicationContext(), "加载数据成功!", Toast.LENGTH_SHORT).show(); Looper.loop(); } }; thread.start();}
会报错
FATAL EXCEPTION: Thread-2456 Process: com.example.imagetouch, PID: 10321 java.lang.RuntimeException: Only one Looper may be created per thread at android.os.Looper.prepare(Looper.java:77) at android.os.Looper.prepare(Looper.java:72) at com.example.imagetouch.MainActivity$1.run(MainActivity.java:23)
Only one Looper may be created per thread
有点熟悉,这个就是上面代码列出的Looper.prepare(boolean quitAllowed)
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));}
如果sThreadLocal.get() != null
就会报错,也就是说,如果当前的线程有了一个Looper,再调用Looper.prepare()之后就会报错。这样做的目的就是实现每一个线程只有一个Looper对象,实现这样的功能就是依靠ThreadLocal这个类了。
这个可以参考理解Java中的ThreadLocal写的很好,
总结来说就是:
每一个Android Thread类里面都有一个
ThreadLocal.Values localValues;
成员变量(java里面是ThreadLocal.ThreadLocalMap threadLocals
,作用差别不大),它保存了ThreadLocal
的值,我们这个事例中保存的就是Looper这个对象。也就是说ThreadLocal
本身是不会保存这个值的,他只是提供了get
(获取设置的值)跟set
(设置一个值进来)这两个方法,这两个方法做得事情就是将Thread作为一个key,设置进来的值作为一个value,以键值对的形式保存(或者取出来)在Thread 的 ThreadLocal.Values成员变量中,而这个ThreadLocal.Values是ThreadLocal的内部类。
参考资料:
http://lavasoft.blog.51cto.com/62575/51926
http://www.iteye.com/topic/103804
http://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/index.html
http://alighters.com/blog/2016/06/25/threadlocal-in-android-message/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
- 由Android Toast 到 ThreadLocal的思考
- Android关于ThreadLocal的思考和总结
- 关于ThreadLocal的思考
- Android 4.2 由Context引发的思考
- 由《图解HTTP》反省的测试用例思考之错误消息toast提示
- 由图形系统设计到软件设计的思考
- 由Timer控件到Windows消息的思考
- 由hosts到域名解析与网站备案的思考
- 由appcampat 的思考
- 由Android系统两个漏洞引起的思考
- 由Android 65K方法数限制引发的思考
- 由Android 65K方法数限制引发的思考
- 由Android 65K方法数限制引发的思考
- 由Android 65K方法数限制引发的思考
- 由Looper中的ThreadLocal谈起--论ThreadLocal的使用
- 由Looper中的ThreadLocal谈起--论ThreadLocal的使用
- Android Toast 设置到屏幕中间,自定义Toast的实现方法,及其说明
- Android Toast 设置到屏幕中间,自定义Toast的实现方法,及其说明
- c中printf的输出问题
- vim详细说明和配置
- 《精通nginx》的两个疑问
- Flume1.7.0的TaildirSource介绍
- 【Python3】【老司机系列】日本dmm网站抓取(一)
- 由Android Toast 到 ThreadLocal的思考
- ArrayDeque源码解析
- 深度学习史上最全总结(文末有福利)
- InheritableThreadLocal用法与ThreadLocal的区别
- 可以直接获取id对象
- 判断number时最好用正则
- 中国剩余定理
- 小知识(1)
- 可能是讲解Android事件分发最好的文章