Android中更新UI的几种方法

前言

众所周知,在安卓中,我们的主线程是不可以进行一些耗时的操作的。因为当主线程超过五秒无响应后,我们的程序就会ANR。但是,在日常开发中,我们又避免不了去做一些耗时的操作。比如说访问网络,文件操作等等。但我们的主线程因为上述原因,却不能轻松完成这些操作,那怎么办?这时候就需要开启新的子线程。有的时候,我们需要把这些操作的结果更新到我们的UI界面上。咦,可是安卓也是规定了的,非UI线程(主线程)是不能更新UI的,那么,我们应该怎么办呢?不要担心,居然有这样的规定,就肯定会有解决的办法嘛= ̄ω ̄=,所以,安卓给我们提供了一种Handler机制。

为什么要这样规定呢

我们以前初中高中做政治题的时候,老师就教我们,遇到问题,首先要知道是什么,然后去想为什么,最后我们再来解决,也就是怎么做。所以,我们可以先来思考一下,为什么安卓中要规定UI界面的更新只能在主线程中呢?
假设一下,如果我们可以在非主线程更新UI,就会造成多个子线程去对我们的UI界面进行操作,那么,我们的界面就会变得非常混乱,界面更新就会存在多并发线程安全的问题。但是我们如果去对UI界面的更新进行加锁的话,我们的性能就会下降。

Android中更新界面的四种方法

方法一:runOnUiThread()

用法:

private void method1() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //耗时操作
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                      //更新界面
                        textView.setText("啦啦啦");
                    }
                });
            }
        }).start();
    }

原理:

点击进入源码查看,我们可以发现它的实现非常简单。首先判断当前线程是不是主线程,如果是,就直接run这个Runnable,如果不是,就调用handler中的Post方法。

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

方法二:handler.post()

用法:

  private void method2() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //耗时操作
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("啦啦啦");
                    }
                });
            }
        }).start();
    }

原理:

其实就是调用了handler的 sendMessageDelayed(),将runnable包装成message交给looper,looper再给handler去处理。所以其实我们也可以通过 sendMessage去更新,我们只需要重写我们的handleMessage就可以了,这里就不再说啦。都是一样的嘛~

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

方法三:View.post()

用法:

      private void method3() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //耗时操作
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                textView.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("啦啦啦");
                    }
                });
            }
        }).start();
    }

原理:

首先判断我们这个mAttachInfo(我猜测是view绑定的线程上),如果不为空我们就调用handler的Post,进入相应的源码,如果为空就去尝试找到。其实不管怎么样,他的目的就是找到可以更新UI的线程的handler。View.postDelayed(Runnable, long)和View.post(Runable)方法一样,只是设置了延迟时间,而View.post(Runable)是立即执行。

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

方法四:AsyncTask

用法:

   private AsyncTask a = new AsyncTask<String,Void,Boolean>(){

       @Override
       protected Boolean doInBackground(String... params) {
           //在后台执行耗时操作
           return null;
       }

       @Override
       protected void onPreExecute() {
           //做一些初始化操作
           super.onPreExecute();
       }

       @Override
       protected void onPostExecute(Boolean aBoolean) {
           //异步任务执行完成后,也就是doInBackground()方法完成后
           //运行再主线程中哦,更新更新
           super.onPostExecute(aBoolean);
       }

       @Override
       protected void onProgressUpdate(Void... values) {
           //由publishProgress内部调用,表示任务进度更新
           //更新更新
           super.onProgressUpdate(values);
       }
   };

原理:

其实就是安卓替我们封装了Handler和Thread,他的使用我们需要注意需要在主线程中创建而且只能在主线程中调用一次execute。而且可能会造成内存泄漏的问题,这时候我们需要用到若引用,或用静态内部类。

我们真的不能在其他线程更新UI嘛?

试试看

我们试试看直接在Thread中更新UI

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.tv_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText("lalalla");
            }
        }).start();
    }

}

咦,当我们运行上面这行代码的时候,UI界面居然被成功更新,而且没有抛出异常。当我们在上面代码中让线程休眠几秒钟,我们的线程又抛出了异常。这到底是为什么呢?

深入源码

当我们在非UI线程更新后,会打印一段

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

也就是说,我们的ViewRootImpl(一个隐藏类)中抛出了异常。根据一层层的深入,我们可以发现源码中有一个checkThread()的方法,这个方法就是用来判断我们的线程是不是在主线程。

void checkThread() {  
       if (mThread != Thread.currentThread()) {  
           throw new CalledFromWrongThreadException(  
                   "Only the original thread that created a view hierarchy can touch its views.");  
       }  
   }  

原来,我们的安卓就是通过这样一个方法来局限我们的。咦,那么问题来了,为什么我们上面那种情况不会报错呢?这就说明,当我们在onCreate的时候我们去调用这个更新UI界面操作的时候,源码还没有去检测我们的线程。这就和我们activity的生命周期有关系了,其实是因为我们的ViewRootImpl是在onResume中去初始化的。这里推荐一篇博客:Android开发经验

各自优缺点:

1.handler机制,结构清晰但是代码过多。
2.asyncTask,简单,快捷,将一些复杂的情况简单化,但是可能会新开一些线程,消耗系统的资源。
3.runOnUiThread,代码简单,需要传递context对象。


一些自言自语:近期目标仍然是:提高学习的效率(但是也不要操之过急哦,不要给自己太大压力,嘻嘻)。然后鼓励一下自己,最近还是有很大的进步!!!明天回家,本来想带电脑回家写博客的,但是今晚写了这篇,那就这两天去梳理一下handler原理和AsyncTask吧,回学校之后就可以整理写一篇博客了。嗯,加油咯~黄雅倩~,嘻嘻,回寝室啦,晚安( ̄o ̄) . z Z

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
机多图,比较漂亮。800*480的。其他尺寸自己改SysInfo.ini文件 X,Y是坐标,IconPushed为图标的地址,SizePushed,SizeNormal图标的大小。 GLOBALSETTINGS ShowMessage = 0 Quitno X = 0 x方向的位置(后同) Y = 0 y方向的位置(后同) Width = 480 桌面背景图片宽度 Height = 272 桌面背景图片高度 Arrange = No 排列与否,和windows桌面排列一样 BackgroundBitmap = \SDMMC\app\PIC\background.bmp 显示桌面背景图片,可以修改成你想显示的图片名称及位置 StartUpAnimation = FlyUp Accelerated = yes TopMost = no #--------------------------------- DATE 显示日期的设置, x = 60 y = 3 Color = FFFFFF 字体颜色(后同) FrameColor = 000000 Size = 18 字体大小(后同) Weight = 700 CreateFont TIME 显示时间的设置, x = 155 y = 3 Color = FFFFFF FrameColor = 000000 Format = HH':'mm':'ss' 时间格式 Size = 18 Weight = 700 CreateFont Interval = 1000 刷新间隔,单位好像是毫秒 BATTERY 显示电池信息, x = 10 y = 5 Width = 38 Height = 16 ColorBattery = A0A0A0 ColorHigh = 00A000 ColorLow = A0A000 ColorCritical = A00000 Segments = 10 电池电量显示分块数 #--------------------------------- #--------------------------------- ICONXPBUTTON 自定义快捷方式,注意每节前后有#----作为分段 x = 420 程序图标的显示位置,X方向 y = 1 程序图标的显示位置,y方向 Command = \SDMMC\应用软件\Off.exe 应用程序位置名称 SizeNormal = 48 图标大小,不能小于图标文件的尺寸 SizePushed = 48 按下去后的图标大小 ScaleAlpha = 100 比例 Ic\SDMMC\图标库\off.ico 快捷方式的图标位置名称 ScaleAlpha = 100 比例 Ic\SDMMC\图标库\off.ico 按下去后的图标位置名称 Quitno 程序启动后是否自动退出,YES是的,no不关闭 TEXT 快捷方式的文字说明 x = 25 文字说明的显示位置,X方向 y = 150 文字说明的显示位置,Y方向 Text = 语音电子书 文字说明的内容 Color = FFFFFF 文字的颜色 FrameColor = 000000 背景框颜色 Size = 16 文字的大小 Weight = 700 CreateFont #--------------------------------- #--------------------------------- ICONXPBUTTON x = 5 y = -16 NewIni = ..\Navi\Navi.ini 运行下一个页面 SizeNormal = 48 SizePushed = 48 S

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值