Android 非同步更新UI的幾種方式
阿新 • • 發佈:2018-12-30
一、為什麼不能在主執行緒更新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");