《第一行代码》第二版 学习总结26 Android中子线程更新UI的三种方式
最近利用下班时间,找了看什么书比较适合初学android的朋友,很多人推荐了这本书,于是就买了一本,感觉看书,思考,动手,再思考和总结这样过程还是很有必要的,于是就打算把自己学习的东西简单的总结一下;方便自己以后查找,也有利于学习的巩固。在这里首先要感谢一下书籍的作者——郭霖前辈。
关于Android中子线程更新UI主要就是实现间通信即可,我们下面将会介绍关于在子线程中更新UI的三种方式;在此之前,你可见简单了解一下java中的多线程相关知识,我在前段时间关于多线程的基本知识介绍了一点(多线程一之基本概念,多线程二之线程生命周期,多线程三之共享数据安全问题,多线程四之死锁与等待唤醒机制);有了对多线程的基本认识,我们就来看看如何实现子线程更改UI(线程间通信)。示例代码下载链接
1,子线程更改UI的方式
上面也说了有三种,分别是:
- Handler实现
- runOnUiThread()
- AsyncTask
其实这几个知识是可以单独拿出来说的,尤其是Handler通过的这种异步消息处理机制,其他的两种方式说白了也是依托于这种机制;就是进行了不同的封装而已,所以这里会简单的介绍一个android一步消息处理机制,其他两个不会展开说明,只会介绍使用步骤。
2,异步消息处理机制
四个部分:
- Message :线程之间传递的消息
- Handler : 发送和处理线程间通信的消息(Message)
- MessageQueue :消息队列,用于存放Handler发送来的消息,等待被处理,一个线程只能有一个消息队列对象
- Looper :管理(监测--loop()方法)消息队列,队列中有消息就取出处理
为了加深一下印象,我自己也画了异步消息处理的流程图(建议看书上的哈哈);其实Android已经帮我们做了很好的封装,使用起来也是非常的方便;下面就来看看具体的使用
3,示例代码
MainActivity.java代码:
package com.hfut.operationuionsubthread; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import java.util.Timer; import java.util.TimerTask; public class MainActivity extends AppCompatActivity { public static final int UPDATE_INFO = 1; public static final int DOWNLOAD_PROGRESS=2; public static final int DOWNLOAD_COMPLETE=3; TextView result; int progress = 0; ProgressBar progressBar; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_INFO: result.setText("Handler:\n我是通过Handler方式修改UI方式"); break; case DOWNLOAD_PROGRESS: result.setText("正在下载,请稍等..."); progressBar.setProgress(progress); break; case DOWNLOAD_COMPLETE: result.setText("下载完成:\nAsyncTask:\n我是通过AsyncTask和Message配合在子线程中实现修改UI的"); Toast.makeText(MainActivity.this,"下载完成",Toast.LENGTH_SHORT).show(); default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); result = findViewById(R.id.tv_result); progressBar=findViewById(R.id.downloadProgress); } //handler方式修改UI public void byHandler(View view) { progressBar.setProgress(0); new Thread(new Runnable() { @Override public void run() { //可以尝试在子线程中直接修改UI //result.setText("我要在子线程中修改UI了,也不知道行不行了"); Message message = new Message(); message.what = UPDATE_INFO; handler.sendMessage(message); } }).start(); } //runOnUiThread方式修改UI public void byRunOnUiThread(View view) { progressBar.setProgress(0); runOnUiThread(new Runnable() { @Override public void run() { result.setText("runOnUiThread:\n我是通过runOnUiThread实现在子线程中修改UI的"); } }); } //AsyncTask方式修改UI public void byAsyncTask(View view) { progress=0; progressBar.setProgress(0); new ChangeUI().execute(); } class ChangeUI extends AsyncTask<Void, Integer, Boolean> { @Override protected Boolean doInBackground(Void... voids) { boolean endTag = true; final Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { Message message = new Message(); progress += 2; if(progress<=10) { message.what = DOWNLOAD_PROGRESS; handler.sendMessage(message); } else{ message.what=DOWNLOAD_COMPLETE; handler.sendMessage(message); timer.cancel(); } } }, 1000, 1000); // try { // Thread.currentThread().sleep(5000); // } catch (InterruptedException e) { // e.printStackTrace(); // } return endTag; } @Override protected void onPostExecute(Boolean aBoolean) { if (aBoolean) { result.setText("AsyncTask:\n我是通过AsyncTask在子线程中实现修改UI的"); } } } }
activity_main.xml代码:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.hfut.operationuionsubthread.MainActivity"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:onClick="byHandler" android:text="handler实现" android:textSize="20dp" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:onClick="byRunOnUiThread" android:text="runOnUIThread实现" android:textSize="20dp" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:onClick="byAsyncTask" android:text="AsyncTask实现" android:textSize="20dp" /> <TextView android:id="@+id/tv_result" android:textSize="15dp" android:layout_width="match_parent" android:layout_height="60dp" android:hint="显示修改UI结果" /> <!--<TextView--> <!--android:layout_marginTop="10dp"--> <!--android:layout_width="match_parent"--> <!--android:layout_height="wrap_content"--> <!--android:text="下载进度"/>--> <ProgressBar android:layout_marginTop="5dp" android:id="@+id/downloadProgress" android:max="10" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
主配置文件AndroidManifest.xml代码:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.hfut.operationuionsubthread"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
代码结构很简单,只有一个活动和一个布局,布局中有三个按钮分别表示三种在子线程中更改UI的方式,其中,第三种我结合了第一种,是一种组合的形式,正常它是通过doInBackground()返回值调用onPostExecute()方法里面的代码来更改UI的,在doInBackground()中是不能修改UI的。
4,运行结果
第一步:运行程序
第二步:点击“HANDLER实现”按钮
第三步:点击“RUNONUITHREDA实现”按钮
第四步:点击“ASYNCTASK实现”按钮
注:
1,在使用AsyncTask的时候,我们需要自定义一个类去继承该抽象类,并传入三个参数,第一个是在执行该任务时需要传入的参数,比如这里执行模拟下载任务,可以传递一个下载URL;第二个表示执行任务的进度单位,第三个是对执行结果的返回,比如这里下载成功返回true,失败返回false。
2,在使用AsyncTask的时候,里面的doInbackground()方法主要处理耗时操作,onProgressUpdate()用于配合第二个参数更新UI;onPostExecute()主要结合doInBackground()返回值处理耗时任务执行完成后的Task收尾工作;
3,为什么要提出异步消息处理机制用于在子线程中修改UI;因为UI数据是异步不安全的;当多个线程操作共享数据(UI)时就会出现数据安全问题;所以才会有这么一个机制;对于多线程数据安全问题可以查看我之前的博客,里面有不错的例子和示例代码。
总结:上面的这些主要目的就是实现不同线程间通信,为的就是解决共享数据安全问题,只不过我们现在使用的都是系统包装好的功能。但是本质还是一样的 。