美文网首页Android开发Android技术进阶Android进阶之路
Android子线程中更新UI【方法+演练】

Android子线程中更新UI【方法+演练】

作者: 谁动了我的代码 | 来源:发表于2023-02-07 20:37 被阅读0次

    theme: channing-cyan

    方法一:用Handler

    1、主线程中定义Handler:

    Handler mHandler = new Handler() {

            @Override  
            public void handleMessage(Message msg) {  
                super.handleMessage(msg);  
                switch (msg.what) {  
                case 0:  
                    //完成主界面更新,拿到数据  
                    String data = (String)msg.obj;  
                      
                    updateWeather();  
                    textView.setText(data);  
                    break;  
                default:  
                    break;  
                }  
            }  
          
        };
    

    2、子线程发消息,通知Handler完成UI更新:

    private void updateWeather() {          
            new Thread(new Runnable(){  
          
                @Override  
                public void run() {  
                    //耗时操作,完成之后发送消息给Handler,完成UI更新;  
                    mHandler.sendEmptyMessage(0);  
                      
                    //需要数据传递,用下面方法;  
                    Message msg =new Message();  
                    msg.obj = "数据";//可以是基本类型,可以是对象,可以是List、map等;  
                    mHandler.sendMessage(msg);  
                }         
            }).start();       
        }
    

    方法一的Handler对象必须定义在主线程中,如果是多个类直接互相调用,就不是很方便,需要传递content对象或通过接口调用;

    方法二:用Activity对象的runOnUiThread方法更新

    在子线程中通过runOnUiThread()方法更新UI:

    new Thread() {  
                public void run() {  
                    //这儿是耗时操作,完成之后更新UI;  
                    runOnUiThread(new Runnable(){  
    
                        @Override  
                        public void run() {  
                            //更新UI  
                            imageView.setImageBitmap(bitmap);  
                        }  
                          
                    });  
                }  
    }.start();
    
    
    
    如果在非上下文类中(Activity),可以通过传递上下文实现调用; 
    
    Activity activity = (Activity) imageView.getContext();  
                        activity.runOnUiThread(new Runnable() {  
    
                        @Override  
                        public void run() {  
                            imageView.setImageBitmap(bitmap);  
                        }  
                    });
    

    这种方法使用比较灵活,但如果Thread定义在其他地方,需要传递Activity对象;

    方法三:View.post(Runnable r)

    imageView.post(new Runnable(){  
    
                        @Override  
                        public void run() {  
                            imageView.setImageBitmap(bitmap);  
                        }  
                          
                    });
    

    这种方法更简单,但需要传递要更新的View过去;

    方法四:AsyncTask

    //UI线程中执行    
    new DownloadImageTask().execute( "www.91dota.com" );    
    private class DownloadImageTask extends AsyncTask {    
        protected String doInBackground( String... url ) {    
             return loadDataFormNetwork( url[0] );//后台耗时操作    
        }    
    
        protected void onPostExecute( String result ) {    
              myText.setText( result ); //得到来自网络的信息刷新页面     
    
       }    
    }
    

    总结:UI的更新必须在主线程中完成,所以不管上述那种方法,都是将更新UI的消息发送到了主线程的消息对象,让主线程做处理;

    演练过程

    Android中线程按功能分的话,可以分为两个,一个是主线程(UI线程),其他的都是子线程

    主线程不能执行那些耗时过长的代码或任务(执行耗时过长的代码会出现应用未响应的提示),所以都是使用子线程来执行耗时过长的代码,比如说下载文件等任务

    一般情况,子线程中执行过长的代码,都是需要进行更新UI操作。

    但是Android中,为了防止安全,是不允许在子线程更新UI的,但是我们可以使用到Android官方给予的API来实现子线程更新UI的操作(本质上,这些API也是切换回了主线程来进行更新UI)

    例子:点击一个按钮,过了1s后完成了下载任务,返回了数据,此数据会显示在界面上

    image

    具体解释:

    点击按钮,之后开启一个子线程来模拟下载过程(线程休眠1s),之后任务执行完毕会返回数据(一个String),使用返回的数据更新UI

    新建一个方法,用来模拟下载任务

    /**
     * 模拟下载
     */
    fun download(): String {
        Thread.sleep(1000)
        return "this is data"
    }
    

    下面的使用6种方式和上面的模拟下载任务的方法,来实现上面例子的效果

    1.Activity.runOnUiThread()

    runOnUiThread是Activity中的方法,只有当前对象是Activity,就可以直接使用,如果当前的对象不是Activity,需要找到Activity对象,才能执行此方法

    runOnUiThread方法的参数为一个Runnable接口,我使用的kotlin,所以有很多东西都是省略了

    设置按钮的点击事件,点击按钮开启一个线程

    btn_start.setOnClickListener {
        thread {
            val data = download()
            runOnUiThread({
                //这里进行更新UI操作
                tv_show.text = data
            })
        }
    }
    

    Java版

    btn_start.setOnClickListener(new OnClickListener(){
        new Thread(new Runnable(){
            String data = download();
            runOnUiThread(new Runnable(){
                @Override
                public void run() {
                    tv_show.setText(data);
                }
            })
        }).start();
    });
    

    2.View.post()

    post方法是View对象的方法,参数也是接收一个runnable接口

    这里我选用的view对象是需要进行更新textview的本身,当然也可以选用其他的View对象,只要是在当前Activity的对象都可以

    btn_start.setOnClickListener {
        thread {
            val data = download()
            //选择当前Activity的View对象都可以
            tv_show.post {
                tv_show.text = data
            }
        }
    }
    

    3.View.PostDelayed()

    此方法和上面的post方法类似,只是多一个参数,用来实现延迟更新UI的效果

    btn_start.setOnClickListener {
        thread {
            val data = download()
            tv_show.postDelayed({
                tv_show.text = data
            },2000)
        }
    }
    

    上面的代码实现的效果是点击按钮之后,过了3s后才会看到界面发生改变

    4.Handler.post()

    new一个Handler对象(全局变量)

    private val handler = Handler()
    

    使用post方法更新UI,此post方法和之前的post方法一样,参数都是为Runnable接口

    btn_start.setOnClickListener {
        thread {
            val data = download()
            handler.post {
                tv_show.text = data
            }
        }
    }
    

    5.AsyncTask(推荐)

    说明

    AsyncTask是一个抽象类,必须创建一个子类类继承它

    这里介绍一下关于AsyncTask的三个泛型参数和几个方法

    泛型参数可以为任意类型,为空的话使用Void

    参数 说明
    params 参数泛型,doInBackground方法的参数
    progress 进度泛型,onProgressUpdate方法的参数
    result 结果泛型,onPostExecute方法的参数

    抽象方法说明:

    方法名 说明
    onPreExectute() 此方法中,常常进行初始化操作,如进度条显示
    doInBackground(Params...) 此方法必须实现,
    onProgressUpdate(Progress...) 进行更新UI的操作
    publishProgress(Progress...) 在doInBackground方法中调用,调用此方法后会回调执行onProgressUpdate方法进行更新UI
    onPostExcute(Result) 任务结束之后进行更新UI

    简单来说,如果子类继承了AsyncTask,它的抽象方法的参数都会变成泛型对应的类型

    例子

    下面的代码是取自我的APP,简单地说明一下AsyncTask

    我传入的是3个泛型参数分别为String,DownloadingItem,DownloadedItem,分别对应的params,progress和result泛型

    这里我是根据自己的需要而两个类DownloadingItem和DownloadedItem,从下面的代码可以看到,抽象方法的参数变为了我们的泛型的类型

    internal inner class DownloadingTask : AsyncTask<String, DownloadingItem, DownloadedItem>() {
        override fun onPreExecute() {
            //一些初始化操作
        }
        override fun doInBackground(vararg params: String?): DownloadedItem {
            //params是一个参数数组,如果创建DownloadingTask对象只传入了一个参数,直接取下标为0的那个即可(需要转型)
            //耗时操作(如下载操作),获得进度数据
            //将新的进度数据传递到onProgressUpdate方法,更新UI
            publishProgress(messageItem)
            //任务执行完毕,返回结果(回调onPostExecute方法)
        }
        override fun onProgressUpdate(vararg values: DownloadingItem?) {
            //这里使用最新的进度来进行相关UI的更新
            //values是一个DownloadingItem数组,取末尾那个即为最新的进度数据
        }
        override fun onPostExecute(result: DownloadedItem?) {
            //下载成功提示或者是其他更新UI的操作
        }
    }
    

    执行:

    执行Task的时候需要在主线程(UI线程调用)

    DownloadingTask().execute("参数")
    

    批量下载:

    //允许在同一时刻有5个任务正在执行,并且最多能够存储50个任务
    private val exec = ThreadPoolExecutor(5, 50, 10, TimeUnit.SECONDS, LinkedBlockingQueue<Runnable>())
    DownloadingTask().executeOnExecutor(exec, url)
    

    6.Handler机制实现(核心)

    其实,Handler机制是子进程更新UI的核心

    我们上面的五种实现子进程更新UI的方式,都是基于Handler机制实现的

    image

    具体机制本文就不多说了,网上有许多的机制说明,这里就只讲一下实现的步骤

    Message中有几个属性,what,arg1,arg2,这三个都是接收一个Int,所以,传递数据不是很友好,这里就不准备实现之前的例子效果了

    what表示来源,arg1和arg2用来传递Int数据

    1.重写Handler类中的handleMessage方法

    一般都是规定好一个Int的常量,来表示what

    private val handler =object : Handler(){
        override fun handleMessage(msg: Message?) {
            if (msg.what == 1) {
                //来源为1,则
            }
        }
    }
    

    2.发送Message

    val msg = handler.obtainMessage()
    //一般都是规定好一个Int的常量,来表示what
    msg.what = 1
    //传递Int数据
    msg.arg1 = 20
    handler.sendMessage(msg)
    
    image

    以上就是AndroidAndroid开发——实现子线程更新UI的一些方法及过程演练;更多有关Android开发的知识进阶可以参考《Android核心技术手册》包含上千个小知识点,30多个模块分布。点击查看

    文末

    更新UI - - 就是改变页面效果,视觉上可以看到的变化。

    线程 - - - 一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行 流就是一个线程。

    主线程 - - 当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main Thread)。需要单独创建的线程都是这个主线程的子线程。(借鉴百度)

    子线程 - - 主线程之外的线程都是子线程。

    相关文章

      网友评论

        本文标题:Android子线程中更新UI【方法+演练】

        本文链接:https://www.haomeiwen.com/subject/fvefkdtx.html