前言
实践是最好的学习方式,技术也如此。
一、同步和异步的概念
1、同步和异步任务
- 同步任务
- 在执行程序时,如果一直没有收到执行结果,就一直等待,不继续往下执行,直到收到执行结果,才接着往下执行;
- 异步任务
- 在执行程序时,如果遇到需要等待的任务,就另外开辟一个子线程去执行它,自己继续往下执行其他程序。子线程有结果时,会将结果发送给主线程。异步任务的形式不影响程序的主要进程。
2、线程
- 是什么
- 就是一个执行过程,多线程即多个执行过程;
- 为什么要有多线程
- 程序中可能有多个任务,如果只有一个线程,那么就只能一个接着一个执行,就类似于同步执行任务,明显执行结果会比较慢,因为只有上一个任务执行完成,才能进行下一个。因此,需要多个线程来分别执行这些任务,即对应了上面的异步任务。
二、Android 多线程与 Handler 机制
1、分类
-
==主线程==
- 对于 Android App 程序。App 一启动,本身就是一个线程,这个线程被称为 主线程(main Thread);
- 负责显示界面与用户进行实时交互(例如,用户点击一个按钮、输入文字,都需要程序进行响应处理);因为界面通常被称为 UI,所以,主线程又被称为 UI 线程。
-
==子线程==
- 除了主线程,剩下开辟的线程都称为子线程;
-
类比生活中的例子
- 场景:小明宴请客人
- 主线程:小明;
- 响应用户对界面的操作:小明接待客人;
- 耗时操作:做饭;
- 开启子线程:请一个保姆来做饭;
- 场景:小明宴请客人
2、原则
- 主线程不能执行网络请求/文件读写等耗时操作,就不能实时响应界面;
- 子线程不能执行
UI
刷新(不能操作UI
元素,操作任何一个控件); - 总结
- 当遇到比较耗时的任务时,主线程可以开启一个子线程去执行,主线程接着执行主界面的响应任务,不影响用户的交互感受;类比,小明接待客人,请一个保姆去做饭,自己继续接待客人,不影响客人的感受;
- 子线程不能更新
UI
界面,就像是保姆不能直接接待客人一样,对用户是不可见的;
3、Handler 机制
1)问题(背景)
- 主线程中开启子线程,子线程执行任务完成将结果传递给主线程;子线程如何将结果(数据)传递给主线程,来实现主线程进行界面的改变?
2)Handler 异步通信系统
Handler
的英文名词解释:处理者;- 主要角色:程序员只需要了解
Handler
与Message
这两个角色即可,剩下两个角色都是安卓系统在底层已经封装好的;Handler
:类的名字;Message
:消息- 子线程执行任务完成将结果传递给主线程,传递的就是这个
Message
实体的对象;
- 子线程执行任务完成将结果传递给主线程,传递的就是这个
MessageQueue
:消息队列Looper
- 承担了主线程不时地查看邮箱中是否有消息的角色;
- 从消息队列中拿消息,只要消息队列中有消息,就会一直不停的拿;
3)消息传递流程
-
主线程在建立的时候就建立了上图所示的一套
Handler
系统,建立的过程是在底层进行的; -
子线程要传递给主线程
消息
,需要通过Handler
;-
子线程会先拿到主线程的
Handler
; -
Handler
通过sendMessage()
方法发送消息,消息发送至消息队列中(而不是直接发送给主线程);- 为什么不直接将消息发送给主线程?(时机的问题)
-
Looper
从消息队列中拿消息,将拿到的消息回传给Handler
,Handler
收到回传回来的消息之后就会执行handlerMessage
方法(处理消息的方法);
-
-
总结
- 主线程的
Handler
系统进行消息流转; - 主线程会从系统中的消息队列中拿到消息交给自己,然后调用
HandleMessage
方法,就可以收到子线程传给主线程的消息了;
- 主线程的
三、代码举例
- 代码背景描述:选择选项框中对应城市的
item
实现查找对应城市的天气数据;
java
// 初始化
private void initView() {
mSpinner = findViewById(R.id.sp_city);
/**
* 设置Adapter
* 获取数据
* 将自定义布局、数据与Adapter进行关联
* 设置Spinner的Adapter
*/
mCities = getResources().getStringArray(R.array.cities);
mSpAdapter = new ArrayAdapter<>(this, R.layout.sp_item_layout, mCities);
mSpinner.setAdapter(mSpAdapter);
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
// 当Spinner中选中某一项时被调用
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
// 选中城市后,会拿到选中城市名字,根据拿到的城市名字来查询对应城市的天气
String selectCity = mCities[i];
getWeatherCity(selectCity); // 开启子线程
}
// 当Spinner没有选中任何项时被调用
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
tvWeather = findViewById(R.id.tv_weather);
tvAir = findViewById(R.id.air);
tvTem = findViewById(R.id.tv_tem);
tvTemLowHigh = findViewById(R.id.tv_tem_low_high);
tvWin = findViewById(R.id.tv_win);
ivWeather = findViewById(R.id.iv_weather);
rlvFutyreWeather = findViewById(R.id.rlv_future_weather);
}
java
// 请求网络(开启一个子线程)
private void getWeatherCity(String selectCity) {
// 开启子线程,请求网络
new Thread(new Runnable() {
@Override
public void run() {
// 请求网络,拿到天气数据
String weatherOfCity = NetUtil.getWeatherCity(selectCity);
// 使用Handler将数据传递给主线程
Message message = Message.obtain(); // 效率高,从消息池里拿消息,利于对象的重用
message.what = 0; // 标识哪个消息
message.obj = weatherOfCity; // 将数据放到消息中
mHandler.sendMessage(message); // 将消息发出去
}
}).start();
}
java
// 主线程处理子线程发送来的消息
private Handler mHandler = new Handler(Looper.myLooper()) {
// 重写里面的方法 ctrl+o
// 重写handleMessage方法
@Override
public void handleMessage(@NonNull Message msg) { // 接收handler发出的消息
super.handleMessage(msg);
if (msg.what == 0) {
// 在主线程中拿到消息
String weather = (String) msg.obj;
Log.d("fan", "---主线程收到了天气数据---" + weather);
// 解析JSON
Gson gson = new Gson();
// 将JSON格式复杂的字符串解析成Java对象
WeatherBean weatherBean = gson.fromJson(weather, WeatherBean.class);
Log.d("fan", "---解析后的数据---" + weatherBean.toString());
updateUiOfWeather(weatherBean);
}
}
};
- 明确:主线程和子线程执行代码的顺序和代码的所属范围;
- 子线程拿到主线程的 Handler,因为 Handler 本身属于是主线程的,所以处理消息(handlerMessage)也是在主线程中(属于主线程范围)
- 红色箭头为主线程执行的代码,黄色框里的为子线程执行的代码范围,两者互不干涉,只执行自己范围内的代码,不能执行范围外的代码;