目录
作者简介:
👨🎓一位20级的计科专业的新手,请各位大佬多多指教
🏡个人主页:XiaoChen_Android
📚学习专栏:Android专栏
🕒发布日期:2022/8/7
1. 概述:
- 进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
为什么要用多线程:由于Android的主线程不能执行耗时操作,如果要进行耗时操作那么必须放在子线程当中,并且子线程中只能进行一些耗时操作,不能去操作我们的UI,更新界面等操作必须在主线程中进行。
1.1 如何把子线程中计算的结果传给主线程?
本文将介绍handler机制来实现多线程中的通信。
2.handler异步通信系统
- Handler机制主要的几个角色:Handler、Message、Looper、MessageQueue(主要用到前两个)
- 主线程在一开始就建立了这么一套系统
流程:
- handler通过SendMessage()方法发送一个消息到消息队列里面(注意不是直接发送给主线程);
- Looper则充当从MessageQueue中循环拿消息,它不停的在循环,只要队列里还有消息它就会不断地从里面拿,拿出来之后把这个消息又传给了handler;
- handler收到之后就会调用handleMessage方法来处理消息
可能会有这个疑问:消息循环一圈又回到了原点,那么为什么不直接发送给handler呢?
可以这么解释:主线程一直在往下运行的过程中,子线程不知道主线程什么时间点有空去处理消息,主线程只能不停的去看有没有消息,它就只能去消息队列里看,而Looper的作用就是帮主线程拿新的消息,这样就不会耽误主线程的运行。
打个比方:消息队列相当于我们家里的邮箱,每条消息相当于邮件,我们就是Looper,邮递员就相当于子线程,因为我们不知道什么时候会有邮件,所以我们只有每天去邮箱里查看有没有新邮件。我们需要一个中转站----MessageQueue。
所以说这个系统很重要,但是我们其实感受不到这个系统,因为Android底层已经帮我们建立好了,在Activity创建时甚至创建之前就已经建立好了,我们只需要使用handler就好了
2.1 多线程通信过程
了解了上面的系统之后,这个流程也就容易理解了
S1:子线程首先拿到主线程的handler
S2:用主线程的handler给主线程发消息---调用SendMessage
S3:主线程通过Looper拿到消息然后调用handleMessage就可以处理消息了
3.代码演示
3.1 子线程部分
//这里主线程只执行了三行代码,其余的都是子线程执行
public void onStart(View view){
//做耗时操作
//创建一个子线程
new Thread(new Runnable() { //这以后主线程会执行
@Override
public void run() { //这里的方法只有子线程才会执行,主线程不会管 子线程决定什么时候执行
String str = getStringFromNet();
//发送消息
Message message = new Message();
message.what = 0; //标识
//这里传递的是一个Stirng类型的 所以类型要用obj的
message.obj = str;
mhandler.sendMessage(message);
}
}) //子线程执行到该处就已经结束
.start(); //主线程会执行
Toast.makeText(this,"任务成功!",Toast.LENGTH_SHORT).show(); //这一行主线程也会执行,并且不需要等待
}
3.2 主线程handler部分
private Handler mhandler = new Handler(Looper.myLooper()){ //这里需要传一个参数Looper.myLooper()否则会有内存泄漏警告 这里必须传主线程自己的looper
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//handler收到消息执行此处
if(msg.what == 0){ //这里就好理解what的作用了
String data = msg.toString();
textView.setText(data);
Toast.makeText(MainActivity.this,"主线程已经收到消息!",Toast.LENGTH_SHORT).show();
}
}
};
3.3 MainActivity代码
package com.example.handler;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button button;
private Handler mhandler = new Handler(Looper.myLooper()){ //这里需要传一个参数Looper.myLooper()否则会有内存泄漏警告 这里必须传主线程自己的looper
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//handler收到消息执行此处
if(msg.what == 0){
String data = msg.toString();
textView.setText(data);
Toast.makeText(MainActivity.this,"主线程已经收到消息!",Toast.LENGTH_SHORT).show();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.tv_content);
button = findViewById(R.id.click);
}
//这里主线程只执行了三行代码,其余的都是子线程执行
public void onStart(View view){
//做耗时操作
//创建一个子线程
new Thread(new Runnable() { //这以后主线程会执行
@Override
public void run() { //这里的方法只有子线程才会执行,主线程不会管 子线程决定什么时候执行
String str = getStringFromNet();
//发送消息
Message message = new Message();
message.what = 0; //标识
//这里传递的是一个Stirng类型的 所以类型要用obj的
message.obj = str;
mhandler.sendMessage(message);
}
}) //子线程执行到该处就已经结束
.start(); //主线程会执行
Toast.makeText(this,"任务成功!",Toast.LENGTH_SHORT).show(); //这一行主线程也会执行,并且不需要等待
}
//为了方便就写死了 如果要网络请求也是在此处写
private String getStringFromNet() {
// 假装从网络获取了一个字符串
String result = "";
StringBuilder stringBuilder = new StringBuilder();
// 模拟一个耗时操作
for (int i = 0; i < 100; i++) {
stringBuilder.append("字符串" + i); //拼接一百个字符串
}
try {
Thread.sleep(5000); //让操作慢下来
} catch (InterruptedException e) {
e.printStackTrace();
}
result = stringBuilder.toString();
return result;
}
}
3.4 布局代码
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="10dp"
android:orientation="vertical"
android:paddingLeft="10dp">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结果"
/>
<Button
android:id="@+id/click"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="开始"
android:onClick="onStart"
android:textAllCaps="false" />
</LinearLayout>
</ScrollView>
4.效果演示
S1:点击开始后会执行一个耗时任务,这里我设置了耗时5秒,但是立马会执行onStart方法,且Toast会立马显示,因为主线程执行的操作不耗时;
S2:经过5秒之后,主线程会收到子线程发出的消息,然后处理消息把文本设置,同时会弹出Toast显示收到消息,到这里通信就已经成功完成了!
5.拓展
进行异步请求还有其他的方式,比如比较流行的是AsyncTask,它是一种轻量级的异步任务,可以在线程池中执行后台任务,然后再把执行的进度和结果传递给主线程并在主线程中更新UI。详细介绍:AsyncTask使用及解析。
推荐一本书:Android开发艺术探索,实践和看书结合效果更好
如果文章对你有帮助就支持一下噢,新手尝试,不好的地方请各位大佬多多指教!