您的位置:首页 > 产品设计 > UI/UE

Android异步更新UI教程总结与demo

2017-04-27 10:54 429 查看

概述

我们在Android开发中可能会遇到下面错误:

Only the original thread that created a view hierarchy can touch its views


意思是我们只能在主线程更新UI,我们知道UI线程(主线程)如果被阻塞5秒,就会ANR,所以我们耗时的操作都会新开启线程,这就必然涉及到后续的UI的更新,今天我们就来讨论下异步更新UI的使用方法总结。主要讲用法,具体的底层实现还是需要自己去慢慢摸索。

分类

1.Activity.runOnUiThread(Runnable);

2.View.post(Runnable),View.postDelay(Runnable,long);

3.Thread+Handler;

4.AsyncTask.

这几种方法底层都是用的Handler,只是封装的不一样。

实践

下面我们就一个一个来讲他们的用法

1.Activity.runOnUiThread(Runnable);

(1)原理:

runOnUiThread(runnable)是Activity的内部类,我们先看看源代码:

public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}


我们可以看到,也是用Handler实现的。我们简单解释下这个类,如果你在UI线程操作,这个动作将立即实现,如果非UI线程,这个动作将Post到UI线程的队列中。

(2)使用:

new Thread(new Runnable() {

@Override
public void run() {
//耗时操作
runOnUiThread(new Runnable() {
public void run() {
//更新UI
}
});

}
}).start();


2.View.post(Runnable)

(1)原理:

源代码

public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}

// 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;
}


我们可以看到也会用到Handler,然后将action加到消息队列中。

(2)用法:

在开启的线程执行下面这个方法

listView.post(new Runnable() {
@Override
public void run() {
listView.setAdapter(new NewsListBaseAdapter(list,MainActivity.this));//更新UI
}
});


3. Thread+Handler;

(1)Handler简介:

我们先来看看Android消息机制:

Android的消息机制主要指 Handler 的运行机制,Handler的运行需要底层的MessageQueue 和 Looper 的支撑。

MessageQueue:消息队列,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作,其内部存储结构采用单链表的数据结构来存储消息列表。

Looper:可理解为消息循环。

由于MessageQueue只是一个消息存储单元,不能去处理消息,而Looper会以无限循环的形式去查找是否有新的消息,如果有的话就处理,否则就一直等待着。

Looper还有一个特殊的概念,就是ThreadLocal,它的作用可以在每个线程中存储数据。

Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Handler内部需要使用ThreadLocal来获取每个线程的Looper。ThreadLocal可以在不同的线程中互不干扰地存储并提供数据。

注意:线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper。主线程,UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,如果对UI控件加锁会有两个确定:首先加上锁机制会使UI访问逻辑变得负责;其次锁机制会降低UI的访问效率,锁机制会阻碍某些线程的执行。鉴于这个两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作,只需要通过Handler切换一下UI访问的执行线程即可。

Handler创建完成后,其内部的 Looper 以及 MessageQueue就可以和Handler一起工作,Handler的post方法将一个 Runnable 投递到 Handler 内部的 Looper中去处理,也可以通过send发送一个消息(post最终也是通过send来完成的)。当Handler的send方法被调用时,它会调用 MessageQueue 的 enqueueMessage 方法将这个消息放入消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的 handleMessage方法就会被调用。注意 Looper 是运行在创建Handler所在的线程中的,这样一来Handler中的业务逻辑就可以切换到创建Handler所在的线程中去执行。

下面我们在来看看handler发送消息的方法:

1.post(Runnable)

2.postAtTime(Runnable,long)

3.postDelay(Runnable,long)

4.sendEmptyMessage(int what)

5.sendMessage(Message)

6.senMessageAtTime(Message,long)

7.sendMessageDelayed(Message,long)

post方式添加一个实现Runnable接口的匿名对象到消息对列中,在目标收到消息后就可以以回调的方式在自己的线程中执行

Message对象所具有的属性:



(2)用法

在子线程发送消息:

Message msg=handler.obtainMessage();
msg.obj=list;//发送了一个list集合
//sendMessage()方法,在主线程或者Worker Thread线程中发送,都是可以的,都可以被取到
handler.sendMessage(msg);


在主线程处理:

handler=new MyHandler();
class MyHandler extends  Handler
{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i(">>>>>>>",Thread.currentThread().getName());
list= (List<NewsBean.Second.Third>) msg.obj;//接收传过来的集合
listView.setAdapter(new NewsListBaseAdapter(list,MainActivity.this));//更新UI
}
}


4. AsyncTask.

(1)简介:

AsyncTask实际上是一个线程池,在代码上比handler要轻量级但是实际上要比Handler要耗资源,Handler仅仅发送了一个消息队列,连线程池对没有开。

主要方法:

1.onPreExecute(),(可选方法)最新用户调用excute时的接口,任务执行之前调用该方法,可以在这里显示进度对话框

2.doInBackground(Params…),后台执行比较耗时的操作,不能直接操纵UI。在该方法中使用

3.publishProgress(progress…)来更新任务的进度。

4.onProgressUpdate(Progress…),在主线程中执行,显示进度条

5.onPostExecute(Result),此方法可以从doinbackground得到的结果来更新UI,在主线程中执行,执行的结果作为参数返回。

6.onCancelled(Object)调用此方法可以随时取消操作。

AsyncTask是个抽象类,定义了三种泛型:

params: 启动任务执行的输入参数,如:http请求的URL

progress:后台任务执行的百分比

result:返回结果,如:String、list集合等

(2)使用:

新建一个类继承AsyncTask,重写方法。

public class NewsListAsyncTask extends AsyncTask<String,Void,List<NewsList>>{
private ListView listView;
private Context context;
public  static  List<NewsList> list;
public NewsListAsyncTask()
{

}

public NewsListAsyncTask (ListView listView,Context context)
{
this.listView=listView;
this.context=context;
}
@Override
protected List<NewsList> doInBackground(String... params) {
//在这里做耗时操作
list=getJsonData(params[0]);
return list;
}
//从网络中获取数据
private List<NewsList> getJsonData(String param) {
List<NewsList> list =new ArrayList<>();
String jsonString="";
try {
jsonString=readString(new URL(param).openStream());
} catch (IOException e) {
e.printStackTrace();
}
NewsList newslist=null;
JSONObject jsonobject=null;
try {
jsonobject=new JSONObject(jsonString);
} catch (JSONException e) {
e.printStackTrace();
}
try {
try {
jsonobject = jsonobject.getJSONObject("result");
} catch (JSONException e) {
e.printStackTrace();
}
JSONArray jsonarray = new JSONArray();
jsonarray = jsonobject.getJSONArray("data");
for (int i = 0; i < jsonarray.length(); i++) {
jsonobject = jsonarray.getJSONObject(i);
newslist = new NewsList();
newslist.realtype=jsonobject.getString("realtype");
newslist.url = jsonobject.getString("url");
newslist.picture = jsonobject.getString("thumbnail_pic_s");
newslist.time = jsonobject.getString("date");
newslist.title = jsonobject.getString("title");
list.add(newslist);
}
} catch (JSONException e) {
e.printStackTrace();
}
return  list;
}
//通过字符流读取
private String readString(InputStream is) {
InputStreamReader isr = null;
String result = "";
String line = "";
try {
isr = new InputStreamReader(is, "utf-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
BufferedReader bufferedReader = new BufferedReader(isr);
try {
while ((line = bufferedReader.readLine()) != null) {
result += line;

}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}

@Override
protected void onPostExecute(List<NewsList> newsLists) {
super.onPostExecute(newsLists);
//在这里更新UI,我这里是用baseadapter往listview中添加数据
NewsListAdapter adapter=new NewsListAdapter(context,newsLists);
listView.setAdapter(adapter);
list=newsLists;
}
}


在主线程中调用:

//URL为访问的网络地址
new OtherNewsListAsyncTask(listView,InternationalNewsActivity.this).execute(URL);


最后附上Demo,里面有这里面其中三种方法的使用,另外demo中有惊喜。

CSDN地址:http://download.csdn.net/detail/simon_crystin/9826686

GitHub地址:https://github.com/Simon986793021/ScienceNews
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: