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

来源:互联网 发布:淘宝客商品采集网站 编辑:程序博客网 时间:2024/06/16 10:10

概述

我们在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

4 1
原创粉丝点击