参考自:https://blog.csdn.net/ydxlt/article/details/51247822 异步更新UI的五种方式
一、为什么不能在主线程更新UI
-
ViewRootImpl通过 checkThread() 方法检查更新UI操作是否是在主线程当中
- 原因:Android的UI是线程不安全的,存在并发访问的问题。加锁也不合适:
- 加锁会让UI访问的逻辑变得复杂
- 加锁会降低UI访问的效率,因为锁会阻塞某些线程的执行
- 原因:Android的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(线程间通讯)(推荐)
- 当在同一个线程中更新多个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");