一. 多线程编程
- 模拟主线程在网络上获取数据,并在主线程UI线程上展示的过程。点击按钮调用
requestEmulator
方法模拟从网络上获取数据的过程,其中主线程休眠一段时间表示正在响应。按钮响应使用布局响应方法。布局文件省略。
.java
public class ThreadActivity extends AppCompatActivity { private String requestString; private TextView tv_content; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); tv_content = findViewById(R.id.thread_tv_content); } public void startClick(View view) { requestString = requestEmulator(); Toast.makeText(this, "获取字符串成功", Toast.LENGTH_SHORT).show(); tv_content.setText(requestString); } public String requestEmulator(){ String result = ""; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 20; i++) { stringBuilder.append("字符串"+i); } try { Thread.sleep(26000); } catch (InterruptedException e) { e.printStackTrace(); } result = stringBuilder.toString(); return result; } }
- 模拟使用子线程来进行网络数据的请求。但此时出现问题,子线程需要和主线程进行通信,主线程负责刷新UI,主线程如果操作UI,子线程还没请求数据完毕主线程UI就刷新了,导致数据无法更新。子线程无法直接操作UI控件,否则会引起闪退。
.java
文件public class ThreadActivity extends AppCompatActivity { private String requestString; private TextView tv_content; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); tv_content = findViewById(R.id.thread_tv_content); } public void startClick(View view) { Thread thread = new Thread(new Runnable() { @Override public void run() { requestString = requestEmulator(); tv_content.setText(requestString); //子线程操作UI报错 } }); thread.start(); //requestString = requestEmulator(); // tv_content.setText(requestString); //主线程操作UI无法刷新数据 Toast.makeText(this, "获取字符串成功", Toast.LENGTH_SHORT).show(); } public String requestEmulator(){ String result = ""; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 20; i++) { stringBuilder.append("字符串"+i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } result = stringBuilder.toString(); return result; } }
二. Handler机制线程间通信
在Android多线程编程中,子线程用于一些耗时操作,例如网络请求,后台下载等。主线程就是UI线程,负责和用户的交互以及数据的呈现。子线程无法操作刷新UI,而如果全部在主线程内操作则会引起长时间等待。因此需要开启子线程并完成线程间通信。使用Handler机制。
Handler由四部分组成,分别是Handler,Message,Looper,MessageQueue。Handler是主线程控制的,发消息是由子线程调用主线程的Handler对象向主线程的优先级消息队列发送消息。每隔固定的时间由Looper将MessageQueue中的消息循环取出,交回主线程handler对象的handleMessage方法处理,处理完成后缓存到消息池中以备复用。发送的消息可以是即时消息,也可以是延迟消息,延迟消息会被延迟一定毫秒来进行处理。消息队列中的优先级则是按照及时和延迟来进行排列的(message对象中的when来表示优先级)。
因此Handler对象可以看做是主线程子线程之间的邮差,信息被封装到Message里,MessageQueue相当于是邮箱,Looper是取信人。
以下代码模拟了handler的处理过程,点击按钮后子线程处理等待数据,处理完毕后将数据封装进message对象使用handler对象发送给主线程,主线程收到消息调用handlemessage处理。
message对象由一个标识码what来表示发信序列,obj封装信息对象。还有两个参数来封装整型数据,这四个属性都是公有的,无需调用set和get方法。
handler.java
public class HandlerActivity2 extends AppCompatActivity { //主线程生成handler对象,并且使用主线程自己的Looper处理消息队列 private Handler handler = new Handler(Looper.myLooper()){ //ctrl+t 可提示方法参数的选择 //ctrl+o 可查看内部拥有的方法,此时选择重写handleMessage方法 @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); if (msg.what == 0){ String info = (String) msg.obj; handle_tv.setText(info); Toast.makeText(HandlerActivity2.this, "消息传递完毕", Toast.LENGTH_SHORT).show(); } } }; private TextView handle_tv; private String requestString; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler2); handle_tv = findViewById(R.id.handle_tv); } public void handleClick(View view) { new Thread(new Runnable() { @Override public void run() { requestString = requestEmulator(); //准备封装消息内容 Message message = new Message(); //Message message = Message.obtain(); message.what = 0; //区分消息发送者 message.obj = requestString; handler.sendMessage(message); } }).start(); Toast.makeText(this, "按钮点击主线程执行完毕", Toast.LENGTH_SHORT).show(); } public String requestEmulator(){ String result = ""; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 20; i++) { stringBuilder.append("字符串"+i); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }result = stringBuilder.toString(); return result; } }
对于相同的 功能,此处有一个尝试,即在子线程中完成数据的请求并直接设置给textview控件,会导致app崩溃。
thread.java
public class ThreadActivity extends AppCompatActivity { private String requestString; private TextView tv_content; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); tv_content = findViewById(R.id.thread_tv_content); } public void startClick(View view) { Thread thread = new Thread(new Runnable() { @Override public void run() { requestString = requestEmulator(); tv_content.setText(requestString); } });thread.start(); //requestString = requestEmulator(); Toast.makeText(this, "获取字符串成功", Toast.LENGTH_SHORT).show(); } public String requestEmulator(){ String result = ""; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 20; i++) { stringBuilder.append("字符串"+i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } result = stringBuilder.toString(); return result; } }