Android 异步更新UI的几种方式

参考自:https://blog.csdn.net/ydxlt/article/details/51247822 异步更新UI的五种方式

一、为什么不能在主线程更新UI

  • ViewRootImpl通过 checkThread() 方法检查更新UI操作是否是在主线程当中

    • 原因:Android的UI是线程不安全的,存在并发访问的问题。加锁也不合适:
      • 加锁会让UI访问的逻辑变得复杂
      • 加锁会降低UI访问的效率,因为锁会阻塞某些线程的执行
  • 直接在子线程修改UI

     @Override
     protected void onResume() {
         super.onResume();
    
         btn.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
    
                 Log.d(TAG, "onClick: " + Thread.currentThread().getName());
    
                 new Thread(new Runnable() {
                     @Override
                     public void run() {
                         
                         Log.d(TAG, "run: 前" + Thread.currentThread().getName());
                         tv.setText("更新后的UI");
                         Log.d(TAG, "run: 后" + Thread.currentThread().getName());
                     }
                 }).start();
    
             }
         });
     }
    
    • API 27可成功修改

      • 原因未知
    • API 23报错

      • android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    • 原因分析:

      • ViewRootImpl类中的checkThread()方法需要匹配Native方法中的Thread
      void checkThread() {
          if (mThread != Thread.currentThread()) {
              throw new CalledFromWrongThreadException(
                      "Only the original thread that created a view hierarchy can touch its views.");
          }
      }
      
      • ViewRootImpl在WindowManager类中的addView()方法中实例化,在Activity的onResume中调用,所以绑定的是主线程

二、异步更新UI的五种常用方式

  • eg:在请求网络图片并更新UI(会引发非UI线程更新UI操作的报错)
public void onClick(View v) { 
    new Thread(new Runnable() { 
        public void run() { 
           Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); 
           mImageView.setImageBitmap(bitmap);     
        } 
    }).start(); 
}
1、Activity.runOnUiThread(Runnable)
  • 只有在Activity中才可使用此方法
public void onClick(View v) { 
    new Thread(new Runnable() { 
        public void run() { 
           Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
           runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mImageView.setImageBitmap(bitmap);  
                }
           });              
        } 
    }).start(); 
}
2、View.post(Runnable)
  • View类及其子类提供了一个post(Runable)方法,允许重写其中的run()更新UI
public void onClick(View v) { 
    new Thread(new Runnable() { 
        public void run() { 
           Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
           imageView.post(new Runnable() {
              @Override
              public void run() {
                  mImageView.setImageBitmap(bitmap);  
              }
           });             
        } 
    }).start(); 
}
3、View.postDelayed(Runnable, long)
  • 与第二种相同,只是多了一个延迟更新的时间(ms为单位)
public void onClick(View v) { 
    new Thread(new Runnable() { 
        public void run() { 
           Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");  
           imageView.postDelayed(new Runnable() {
              @Override
              public void run() {
                  mImageView.setImageBitmap(bitmap);  
              }
           },2000);          
        } 
    }).start(); 
}
4、使用handler(线程间通讯)(推荐)

参考:https://blog.csdn.net/iispring/article/details/47115879

  • 当在同一个线程中更新多个UI时使用
  • 一定要在主线程中定义接收
    • 每个Hanlder都关联了一个线程,每个线程内部都维护了一个消息队列MessageQueue,这样Handler实际上也就关联了一个消息队列
    • 在执行new Handler()的时候,默认情况下Handler会绑定当前代码执行的线程程
//发送消息

new Thread(new Runnable() { 
    public void run() { 
        Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); 
        
        //网络请求任务结束后执行下面的代码发送Message
        Message message = mHandler.obtainMessage();
        message.what = 1;
        //传递对象
        message.obj = bitmap;
        mHandler.sendMessage(message);        
    } 
}).start();
//主线程中定义接收

Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 1:
                Bitmap bitmap = (Bitmap) msg.obj;
                imageView.setImageBitmap(bitmap);
                break;
            case 2:
                // ...
                break;
            default:
                break;
        }
    }
};
5、AsyncTask(推荐)
  • doInBackground
    • 参数类型是AsyncTask.execute方法的参数
    • 后台(子线程)执行耗时操作,返回值将作为onPostExecute方法的参数
  • onPostExecute
    • UI线程执行
    • 参数类型是doInBackground方法的返回值类型,即后台处理耗时操作返回的结果
//定义AsyncTask

AsyncTask<String,Void,Bitmap> asyncTask = new AsyncTask<String, Void, Bitmap>() {

    /**
     * 即将要执行耗时任务时回调,这里可以做一些初始化操作
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 在后台执行耗时操作,其返回值将作为onPostExecute方法的参数
     * @param params
     * @return
     */
    @Override
    protected Bitmap doInBackground(String... params) {
        Bitmap bitmap = loadImageFromNetwork(params[0]);
        return bitmap;
    }

    /**
     * 当这个异步任务执行完成后,也就是doInBackground()方法完成后,
     * 其方法的返回结果就是这里的参数
     * @param bitmap
     */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        imageView.setImageBitmap(bitmap);
    }
};
//调用

asyncTask.execute("http://example.com/image.png");
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值