Android——「异步任务与多线程」

前言

实践是最好的学习方式,技术也如此。

一、同步和异步的概念

1、同步和异步任务

  • 同步任务
    • 在执行程序时,如果一直没有收到执行结果,就一直等待,不继续往下执行,直到收到执行结果,才接着往下执行;
  • 异步任务
    • 在执行程序时,如果遇到需要等待的任务,就另外开辟一个子线程去执行它,自己继续往下执行其他程序。子线程有结果时,会将结果发送给主线程。异步任务的形式不影响程序的主要进程。

2、线程

  • 是什么
    • 就是一个执行过程,多线程即多个执行过程;
  • 为什么要有多线程
    • 程序中可能有多个任务,如果只有一个线程,那么就只能一个接着一个执行,就类似于同步执行任务,明显执行结果会比较慢,因为只有上一个任务执行完成,才能进行下一个。因此,需要多个线程来分别执行这些任务,即对应了上面的异步任务。

二、Android 多线程与 Handler 机制

1、分类

  • ==主线程==

    • 对于 Android App 程序。App 一启动,本身就是一个线程,这个线程被称为 主线程(main Thread);
    • 负责显示界面与用户进行实时交互(例如,用户点击一个按钮、输入文字,都需要程序进行响应处理);因为界面通常被称为 UI,所以,主线程又被称为 UI 线程。
  • ==子线程==

    • 除了主线程,剩下开辟的线程都称为子线程;
  • 类比生活中的例子

    • 场景:小明宴请客人
      • 主线程:小明;
      • 响应用户对界面的操作:小明接待客人;
      • 耗时操作:做饭;
      • 开启子线程:请一个保姆来做饭;

2、原则

  • 主线程不能执行网络请求/文件读写等耗时操作,就不能实时响应界面;
  • 子线程不能执行 UI 刷新(不能操作 UI 元素,操作任何一个控件);
  • 总结
    • 当遇到比较耗时的任务时,主线程可以开启一个子线程去执行,主线程接着执行主界面的响应任务,不影响用户的交互感受;类比,小明接待客人,请一个保姆去做饭,自己继续接待客人,不影响客人的感受;
    • 子线程不能更新 UI 界面,就像是保姆不能直接接待客人一样,对用户是不可见的;

3、Handler 机制

1)问题(背景)

  • 主线程中开启子线程,子线程执行任务完成将结果传递给主线程;子线程如何将结果(数据)传递给主线程,来实现主线程进行界面的改变?

2)Handler 异步通信系统

  • Handler 的英文名词解释:处理者;
  • 主要角色:程序员只需要了解 HandlerMessage 这两个角色即可,剩下两个角色都是安卓系统在底层已经封装好的;
    • Handler:类的名字;
    • Message:消息
      • 子线程执行任务完成将结果传递给主线程,传递的就是这个 Message 实体的对象;
    • MessageQueue:消息队列
    • Looper
      • 承担了主线程不时地查看邮箱中是否有消息的角色;
      • 从消息队列中拿消息,只要消息队列中有消息,就会一直不停的拿;

3)消息传递流程

  • 主线程在建立的时候就建立了上图所示的一套 Handler 系统,建立的过程是在底层进行的;

  • 子线程要传递给主线程消息,需要通过 Handler

    • 子线程会先拿到主线程的 Handler

    • Handler 通过sendMessage() 方法发送消息,消息发送至消息队列中(而不是直接发送给主线程);

      • 为什么不直接将消息发送给主线程?(时机的问题)
    • Looper 从消息队列中拿消息,将拿到的消息回传给 HandlerHandler 收到回传回来的消息之后就会执行 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)也是在主线程中(属于主线程范围)
  • 红色箭头为主线程执行的代码,黄色框里的为子线程执行的代码范围,两者互不干涉,只执行自己范围内的代码,不能执行范围外的代码;
相关推荐
Dnelic-2 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen4 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年11 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿14 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神15 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛15 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法16 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter17 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快18 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl18 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5