子线程弹出toast引发思考如何保证
点击按钮我们直接调用下面方法
private fun showToast1() {
thread {
Toast.makeText(this@HandlerTestActivity,"toast",Toast.LENGTH_SHORT).show()
}
}
大家都知道会报错
Can't toast on a thread that has not called Looper.prepare()
是的,提示的是我们不是子线程不能更新UI而是上面的提示
为什么呢?带着疑问我们去看下源码
查看源码
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
return makeText(context, null, text, duration);
}
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
XXX
XXX
XXX
}
第二个入参是一个null的 Looper
我们继续查看
Toast result = new Toast(context, looper);
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
mTN = new TN(context.getPackageName(), looper);
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
TN中传入的looper,我们继续点进去查看TN的构造方法
TN(String packageName, @Nullable Looper looper) {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mPackageName = packageName;
if (looper == null) {
// Use Looper.myLooper() if looper is not specified.
looper = Looper.myLooper();
if (looper == null) {
throw new RuntimeException(
"Can't toast on a thread that has not called Looper.prepare()");
}
}
我们直接看到我们截止的地方,先判断构造方法的looper是否为空
我们没有传,所以looper.myLooper()获取当前的线程的looper
主线程直接可以使用是因为,主线程的Looper在ActivityThread里面就已经创建好了
但是子线程是没有Looper的,我们通过改造方法
private fun showToast2(){
thread {
if (Looper.myLooper()==null) {
Looper.prepare()
}
Toast.makeText(this@HandlerTestActivity,"toast",Toast.LENGTH_SHORT).show()
Looper.loop()
}
}
解决问题 toast成功弹出
但是我的思考才刚刚开始
我们知道Looper每个线程最多只有一个
每个Looper里面有个sThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
他是被final修饰的所以只有一个
我们创建Looper的时候
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的set和get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
如何保证Looper唯一?
我们看到了上面的set函数所有的key都是唯一的sThreadLocal,但是所有每个线程有自己的ThreadLocalMap
所以线程1<k,V> sThreadLocal looper1
线程2<k,V> sThreadLocal looper2
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}