1.问题背景
因为Android官方规定不能直接在子线程(工作线程)中更新UI,所以需要考虑如果在子线程中更新界面时,如何进行UI的刷新显示操作,下面根据开发的经历,总结一下几种在子线程中如何进行UI的更新操作。
2.Android系统不允许直接在子线程中进行UI操作的原因:
- 多线程特点决定
在多线程的程序中,对CPU的资源具有抢占性,也就是多个线程并发的运行时,多线程操作处理不当可能会导致数据的不一致,而界面是数据的直接反映。所以这个规定主要是避免多线程的并发可能引起的更新界面错乱的问题。 - 规避多线程安全问题
Android开发很多地方用到多线程的机制,如果在一个Activity中存在多个线程更新UI,而此时Activity中没有使用一种"加锁"机制,那么就可能会产生界面显示错乱的问题。当然google采用的是禁止在子线程中更新UI,每次需要更新ui时都将更新任务放到主线程中。
3.Android sdk提供的几种子线程中更新UI的方式
下面是Android开发中几种常见的子线程中更新UI的方式,当然这里说的在子线程中更新ui本质上还是从子线程中转到主线程中执行更新UI的操作,所以这个问题本质上也就是在多线程编程时,怎么实现线程的切换操作,只是这个问题更加具体化,只是从子线程向主线程中切换。
一,Handler机制
Handler的作用
handler是Android提供的用来更新UI的一套机制,也是一套消息处理机制,可以发送消息,也可以通过特定的方法接受并处理消息,handler封装了一套消息处理机制。
Android在设计的时候就封装了一套消息机制,包括消息创建、传递、处理机制,凭借这个机制就能很通过很简单的编码实现消息的处理机制。
几个常用的handler发送消息的方法:
//发送消息
public final boolean sendEmptyMessage(int what)
public final boolean sendMessage(Message msg)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
//发送任务 runnable中实现具体的逻辑代码,会运行在主线程中
public final boolean post(Runnable r)
public final boolean postDelayed(Runnable r, long delayMillis)
举例: 实现图图片轮播效果的代码示例:
public class MainActivity extends Activity {
//图片id
public int resId[] = { R.drawable.kola, R.drawable.qie, R.drawable.yujinxiang };
//图片id下标
private int index;
private ImageView mImageView;
// 通过Handler机制更新UI
private Handler handler = new Handler();
// 创建一个实现图片轮流显示的Runnable对象
class PictureTask implements Runnable {
@Override
public void run() {
index ++;
index %= 3;
mImageView.setImageResource(resId[index]);
handler.postDelayed(task, 1000);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageView1);
PictureTask task= new PictureTask ();
handler.postDelayed(task, 2000);
}
}
当需要在子线程中创建Handler对象,并需要处理消息时,就要自己手动编码创建Looper,因为只有UI线程是系统已经将Looper创建好了的。创建方法很简单:
Looper.prepare();
Handler handler=new Handler();
Looper.loop()
或者:Handler handler=new Handle(Looper.getMainLooper());
即可得到在子线线程中的创建消息队列和消息轮询(Looper)。
另外在子线程中创建looper也是可行的:
new Thread().start(new Runnable() {
@Override
public void run() {
Looper.prepare();
doRefreshView(); //runOnUiThread
Looper.loop();
}
});
二,AsyncTask工具
- 1.AsyncTask 提供的接口:
public abstract class AsyncTask<Params, Progress, Result> {
@MainThread
protected void onPreExecute() {
}
@WorkerThread
protected abstract Result doInBackground(Params... params);
@MainThread
protected void onPostExecute(Result result) {
}
@MainThread
protected void onProgressUpdate(Progress... values) {
}
}
- 2.参数说明:
**Prams:**启动任务时输入参数类型
**Progress:**后台任务执行过程中的进度值类型
**Result:**后台执行任务完成后返回值类型
回调方法说明:
**doInBackground:**必须重写,异步执行后台线程将要完成的任务
**onPreExecute:**执行完后台操作前被调用,初始化操作–准备工作
**onPostExecute:**完成后被调用
**onProgressUpdate:**被doInBackground调用,更新进度
说明:除doInBackground方法运行在子线程中,其他都在主线程中。
调用顺序: onPreExecute->doInBackground->onPostExecute
使用方法示例:
public class MainActivity extends Activity {
private WebView webView;
private static final String URL = "http://www.baidu.com";
public static String test = null;
private TestAsync mTestAsync;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = (WebView) findViewById(R.id.webView);
//注:execute()在子线程中调用会报错
mTestAsync = new TestAsync();
mTestAsync .execute(URL);
}
class TestAsync extends AsyncTask<String, Integer, String> {
private String result = "";
HttpURLConnection httpConn = null;
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* doInBackground在子线程中执行耗时操作
* 除了此方法之外的其他方法都运行在主线程中
*
*/
@Override
protected String doInBackground(String... params) {
try {
URL url = new URL(params[0]);
httpConn = (HttpURLConnection) url.openConnection();
//设置网络连接超时
httpConn.setConnectTimeout(3000);
//设置网络连接请求方法为get
httpConn.setRequestMethod("GET");
InputStreamReader isr = new InputStreamReader(httpConn.getInputStream(), "utf-8");
BufferedReader br = new BufferedReader(isr);
String line = "";
while ((line = br.readLine()) != null){
result += line;
}
//关闭IO和连接
ins.close();
br.close();
httpConn.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* onProgressUpdate和onPostExecute方法在主线程中执行
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(String result) {
test = result;
super.onPostExecute(result);
Toast.makeText(getApplicationContext(), "下载完成!", Toast.LENGTH_SHORT).show();
webView.loadData(result.toString(), "text/html;charset=utf-8", null);
}
}
@Override
protected void onDestroy() {
if (mTestAsync != null && !mTestAsync.isCancelled()) {
mTestAsync.cancel();
mTestAsync = null;
}
super.onDestroy();
}
}
三,runOnUiThread()方法
android还提供了一种简便的方式进行线程的切换,查看源码我们知道该方法如下:
/**
* 如果当前的操作是在UI线程则立即执行,如果当前操作在非UI线程中,则当前任务被发送到UI线程的消息队列中等待执行
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
*
* @param action the action to run on the UI thread
*/
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
四,三种方式的优缺点
首页,以上三种方式都能实现主线程、子线程的切换,本质上都是使用了Handler消息机制(可以从源码中看出),那么这三种方法在实际开发中各自的优虐是什么呢?
- 1.Handler和AsyncTask: 需要注意如果非静态内部类的方式创建实例,会持有Activity的引用,引起内存泄漏,注意不要直接对Activity进行强引用,可以换成WeakReference的方式应用即可。
- 2.runOnUiThread()是定义在Activity中,如果拿不到Activity实例就无法使用。