由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
miaoshu
我去我显示一个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

0 0
原创粉丝点击