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

概述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AnjoyZhang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值