为什么子线程中可以Toast和更新Progressbar进度?

问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也可以在子线程中更新进度。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值