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)也是在主线程中(属于主线程范围)
  • 红色箭头为主线程执行的代码,黄色框里的为子线程执行的代码范围,两者互不干涉,只执行自己范围内的代码,不能执行范围外的代码;
相关推荐
fatiaozhang952731 分钟前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO2 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师2 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师2 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫2 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白2 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong4 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519875 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android
老狼孩111225 小时前
2025新版懒人精灵零基础及各板块核心系统视频教程-全分辨率免ROOT自动化开发
android·机器人·自动化·lua·脚本开发·懒人精灵·免root开发
打死不学Java代码6 小时前
PaginationInnerInterceptor使用(Mybatis-plus分页)
android·java·mybatis