问1:子线程中能更新UI吗?
Android多线程机制,我们知道,子线程中不能有更新UI界面的操作,UI线程不能进行一些耗时操作。所以显然是不能的。
接着问2:子线程中能直接Toast和更新Progressbar进度吗?
Toast和更新Progressbar不也是UI更新吗,大部分人会认为那肯定是不能呀!不然不就和问题1相违背了吗?然而事实是可以Toast和更新Progressbar的。
说明:两个问题并不冲突。我们有时间的话可以查看Toast和Progressbar的源码便知道:他们最后还是通过Handler的方式在主线程中操作的,并没有违背Android的多线程原则。
注:源码api为26
1.Toast
Toast.makeText(context, "Toast", Toast.LENGTH_SHORT).show();
查看源码:
构造方法:
public Toast(Context context) {
this(context, null);
}
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
mTN = new TN(context.getPackageName(), looper);//创建一个TN对象,看到looper就可以知道TN里肯定创建了一个Handler对象。
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对象(Transient Notification)
if (looper == null) {
// Use Looper.myLooper() if looper is not specified.
looper = Looper.myLooper();//默认会拿当前线程的looper对象
if (looper == null) {
throw new RuntimeException(//没有会报错,所以子线程调用Toast之前必须先初始化looper,调用Looper.prepare()。而主线程不用。
"Can't toast on a thread that has not called Looper.prepare()");
}
}
mHandler = new Handler(looper, null) {...}
/**初始化布局,构建Toast**/
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
Toast result = new Toast(context, looper);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
show()方法,handler发消息到主线程Toast。
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();//发消息。
}
2.ProgressBar(参考文章:ProgressBar 深入分析)
初始化
ProgressBar pb = new ProgressBar(this); // 初始化一个ProgressBar
更新进度调用:setProgress()
public synchronized void setProgress(int progress) {
setProgressInternal(progress, false, false);
}
synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) {
if (mIndeterminate) {
// Not applicable.
return false;
}
progress = MathUtils.constrain(progress, mMin, mMax);
if (progress == mProgress) {
// No change from current.
return false;
}
mProgress = progress;
refreshProgress(R.id.progress, mProgress, fromUser, animate);
return true;
}
可以看到更新进度最终调用了refreshProgress()方法。进入方法:
if (mUiThreadId == Thread.currentThread().getId()) {
doRefreshProgress(id, progress, fromUser, true, animate);
}
方法中先判断了当前是否是UI线程,如果是就直接调用 doRefreshProgress(id, progress, fromUser, true, animate);
更新进度。
else {
if (mRefreshProgressRunnable == null) {
mRefreshProgressRunnable = new RefreshProgressRunnable();
}
final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
mRefreshData.add(rd);
if (mAttached && !mRefreshIsPosted) {
post(mRefreshProgressRunnable);
mRefreshIsPosted = true;
}
}
不是主线程的话调用上述方法。经过一系列。。。(忽略,有兴趣的话可以研究一下Progressbar的机制)判断之后走了 post(mRefreshProgressRunnable);
方法。这个方法是父类(progressbar继承了view)view的方法。也就是View.post(Runnable runnable)方法。查看源码:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);//handler找到了
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
View.post()方法是可以进行刷新ui的。(可参考博客:View.post()到底干了啥)
最终Handler在这里被找到了,该handler绑定在主线程上,所以还是在主线程上更新的进度。除此之外Seekbar也可以在子线程中更新进度。