由Android Toast 到 ThreadLocal的思考

由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
 */
@Override
public void show() {
    if (localLOGV) Log.v(TAG, "SHOW: " + this);
    mHandler.post(mShow);
}

/**
 * schedule handleHide into the right thread
 */
@Override
public void hide() {
    if (localLOGV) Log.v(TAG, "HIDE: " + this);
    mHandler.post(mHide);
}

可知这里只是使用到了Handler的消息循环的机制(自己发送消息,然后自己处理消息)。当然这里就需要使用到Looper,所以需要调用Looper.prepare()(为当前线程设置一个Looper对象)以及调用Looper.loop()方法(使得整个消息可以循环起来)。假设我们这样调用

@Override
protected 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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值