安卓基础巩固(三)多线程、IO操作、数据存储

news2025/1/12 21:07:45

文章目录

  • 多线程
    • Handler
    • 相关概念
      • UI线程/主线程
      • Message
      • Message Queue
      • Looper
      • Handler
    • 使用步骤
      • Handler.sendMessage()
      • Handler.post()
    • Handler 机制工作原理
    • Handler内存泄露
      • 前置知识
      • 案例分析
      • 解决方案一:静态内部类+弱引用
      • 解决方案一:onDestroy()时清空Handler内的消息队列。
    • AsynTask
    • 线程间通信

多线程

多线程的应用在Android开发中是非常常见的,常用方法主要有:
在这里插入图片描述

Handler

Handler是Android开发中的一种线程间消息传递机制,使用Handler可以在多个线程并发更新UI的同时,保证线程安全。

相关概念

UI线程/主线程

在Android开发中,为了UI操作是安全的,规定只允许UI线程更新Activity里的UI组件,UI线程也是主线程!
UI线程在程序第1次启动时自动开启,处理与UI相关的事件。

Message

Message是线程间通信的数据单元,存储需要操作的通信信息。

Message Queue

一种先进先出,用于存储Handler发送过来的Message的数据结构。

Looper

Looper直译为循环器,它是Message Queue与Handler之间的通信媒介,Looper是持有MessageQueue的,它的作用包含:

  • 消息获取:循环取出Message Queue中的消息。
  • 消息分发:将取出的Message发送给对应的Handler。

每个线程中只能拥有一个Looper,一个Looper可绑定多个线程的Handler,也就是说,多个线程的Handler可以向一个线程的Looper所持有的MessageQueue中发送Message。

Handler

Handler直译为处理者,它是子线程与主线程间的通信媒介,线程消息的主要处理者。Handler持有 Looper 的实例,直接持有looper的消息队列。

  • Handle可以添加Message到Message Queue。
  • 可以处理Looper分发来的消息。

在这里插入图片描述

使用步骤

Handler.sendMessage()

Handler.sendMessage()的入参是Message对象,可以在工作线程中使用Handler.sendMessage(),发送Message,然后在主线程中接收Message,执行更新UI的操作。

可以通过新建Handler的子类或者构建匿名内部类的形式实现。

/** 
  * 方式1:新建Handler子类(内部类)
  */

    // 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法
    class mHandler extends Handler {

        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
         ...// 需执行的UI操作
            
        }
    }

    // 步骤2:在主线程中创建Handler实例
        private Handler mhandler = new mHandler();

    // 步骤3:创建所需的消息对象
        Message msg = Message.obtain(); // 实例化消息对象
        msg.what = 1; // 消息标识
        msg.obj = "AA"; // 消息内容存放

    // 步骤4:在工作线程中 通过Handler发送消息到消息队列中
    // 可通过sendMessage() / post()
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable
        mHandler.sendMessage(msg);

    // 步骤5:开启工作线程(同时启动了Handler)
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable


/** 
  * 方式2:匿名内部类
  */
   // 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
            private Handler mhandler = new  Handler(){
                // 通过复写handlerMessage()从而确定更新UI的操作
                @Override
                public void handleMessage(Message msg) {
                        ...// 需执行的UI操作
                    }
            };

  // 步骤2:创建消息对象
    Message msg = Message.obtain(); // 实例化消息对象
  msg.what = 1; // 消息标识
  msg.obj = "AA"; // 消息内容存放
  
  // 步骤3:在工作线程中 通过Handler发送消息到消息队列中
  // 多线程可采用AsyncTask、继承Thread类、实现Runnable
   mHandler.sendMessage(msg);

  // 步骤4:开启工作线程(同时启动了Handler)
  // 多线程可采用AsyncTask、继承Thread类、实现Runnable

Handler.post()

Handler.post()的入参是一个Runnable对象,重写Runnable对象的run()方法,进行UI的更新。
下边是一个利用handle的postDelayed实现的验证码倒计时。

Handler countHandler = new Handler(); //验证码发送等待倒计时
    //    发送验证码倒计时
    private final Runnable myRunnable = new Runnable() {
        @Override
        public void run() {
            if(wait >=0) {
                sendCodeButton.setText(String.valueOf(wait));
                countHandler.postDelayed(this, 1000);
                //从当前时间开始延迟delayMillis时间后执行Runnable
                wait--;
            }
            else{
                wait = 60;
                sendCodeButton.setText("重新发送");
                sendCodeButton.setEnabled(true);
                countHandler.removeCallbacks(this);
            }
        }
    };
countHandler.postDelayed(myRunnable,1000);

Handler 机制工作原理

Handler机制的工作流程主要包括4个步骤:

  • 异步通信准备
  • 消息入队
  • 消息循环
  • 消息处理
    在这里插入图片描述
    一个线程只能有一个Looper,但可以绑定到多个Handler。在工作进程通过Handler发送Message到消息队列中,Looper将消息再传递给创建该消息的Handler,Handler在主线程中接收到消息,并执行UI操作。
    在这里插入图片描述

Handler内存泄露

前置知识

  1. 内存泄露出现的原因:
    当一个对象已经不再使用时,本身被回收但却因为有另外一个正在使用的对象持有它的引用,从而导致它不能被回收,这就导致了内存泄露。
  2. 主线程的Looper对象的生命周期 = 该应用程序的生命周期
    在java中,非静态内部类匿名内部类都默认持有外部类的引用(可以调用this)

案例分析

在java中如果按照下边的方式使用Handler会警告出现内存泄露。

     public class MainActivity extends AppCompatActivity {

        public static final String TAG = "carson:";
        private Handler showhandler;

        // 主线程创建时便自动创建Looper & 对应的MessageQueue
        // 之后执行Loop()进入消息循环
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            //1. 通过匿名内部类实例化的Handler类对象
            //注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
            showhandler = new  Handler(){
                // 通过复写handlerMessage()从而确定更新UI的操作
                @Override
                public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case 1:
                                Log.d(TAG, "收到线程1的消息");
                                break;
                            case 2:
                                Log.d(TAG, " 收到线程2的消息");
                                break;
                        }
                    }
            };

出现内存泄露的原因:

  • 在Handler消息队列还有未处理的消息/正在处理的消息时,消息队列中的Message持有Handler实例的引用(Message是Handler的内部类)。
  • 由于Handle 是非静态内部类,那么它又持有外部MainActivity类的引用。
  • 上述引用关系会一直保存,直到Handler消息队列中的消息被处理完毕。
  • 如果在Handler消息队列还有未处理的消息,用户关闭了APP,此时需要销毁MainActivity,但是由于上述引用关系,导致垃圾回收器无法回收MainActivity,从而导致内存泄露。

在这里插入图片描述

解决方案一:静态内部类+弱引用

  1. 使用静态内部类构建Handler实例,并使用WeakReference弱引用持有外部类,保证外部类能被回收。因为:弱引用的对象拥有短暂的生命周期,在垃圾回收器线程扫描时,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:";
    private Handler showhandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 实例化自定义的Handler类对象->>分析1
        // 注:
            // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
            // b. 定义时需传入持有的Activity实例(弱引用)
        showhandler = new FHandler(this);

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1;// 消息标识
                msg.obj = "AA";// 消息存放

                showhandler.sendMessage(msg);
            }
        }.start();

    }

    // 设置为:静态内部类
    private static class FHandler extends Handler{

        // 定义 弱引用实例
        private WeakReference<Activity> reference;

        // 在构造方法中传入需持有的Activity实例
        public FHandler(Activity activity) {
            // 使用WeakReference弱引用持有Activity实例
            reference = new WeakReference<Activity>(activity); }

        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    Log.d(TAG, "收到线程1的消息");
                    break;
                case 2:
                    Log.d(TAG, " 收到线程2的消息");
                    break;


            }
        }
    }
}

解决方案一:onDestroy()时清空Handler内的消息队列。

  1. 当外部类结束生命周期,清空Handler内的消息队列。
@Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
        // 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
    }

activity被销毁前一定会执行onDestroy方法,在此处清空Handler内的消息队列,就可以保证Handler被正常销毁。

为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即 静态内部类 + 弱引用的方式

AsynTask

AsyncTask是一个在不需要开发者直接操作多线程和Handler的情况下的帮助类,适用于短时间的操作(最多几秒)。

线程间通信

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day09 MyBatis基础操作

为什么使用框架 框架的作用:可以直接调用写好的API,提高开发效率,框架是一种经过校验,有一定功能的半成品软件 为什么选择mybatis框架 它几乎避免了所有的JDBC的代码和手动设置参数及获取结果集.作用于持久层,支持定制化sql,存储过程及高级映射 项目所需要的jar包: lombok…

PowerShell系列(四):PowerShell进入交互环境的三种方式

目录 1、Win键X 方式 2、使用微软自带的搜索功能 3、命令行运行方式 4、命令行窗口方式 5、使用第三方命令行软件&#xff08;Terminal&#xff09;开启PowerShell环境 6、PowerShell交互环境执行脚本的一些优势 7、小技巧 今天继续给大家讲解PowerShell相关的知识&…

IMX6ULL裸机篇之DDR3实验-更新 imxdownload.h

一. DDR实验 之前关于 IMX6ULL开发板&#xff0c;有关DDR实验。做了DDR内存芯片的初始化&#xff0c;校验与超频测试。 博文链接如下&#xff1a; IMX6ULL裸机篇之DDR3初始化_凌雪舞的博客-CSDN博客 IMX6ULL裸机篇之DDR3校验与超频测试_凌雪舞的博客-CSDN博客 经过了初始化…

C Primer Plus第二章编程练习答案

学完C语言之后&#xff0c;我就去阅读《C Primer Plus》这本经典的C语言书籍&#xff0c;对每一章的编程练习题都做了相关的解答&#xff0c;仅仅代表着我个人的解答思路&#xff0c;如有错误&#xff0c;请各位大佬帮忙点出&#xff01; 1.编写一个程序&#xff0c;调用一次 …

【Linux系列P3】Linux的权限有什么重点?一文带你理清!

前言 大家好&#xff0c;这里是YY的Linux系列part3&#xff1b;本章主要内容面向能使用Linux的老铁&#xff0c;主要内容是【Linux的用户&用户指令】【文件属性】【权限&权限值的两者表达方式】【权限相关指令】【起始权限问题与掩码问题】【粘滞位的概念与特殊权限-t解…

[Ext JS3.9] 标签面板(TabPanel )介绍与开发

标签面板 标签面板是RIA 类应用使用较多的组件, 因为其可以显示的内容比较多。 标签面板的效果如下图: 标签面板开发也很简单, 配置式的开发代码类似: {xtype: tabpanel,items: [{title: 标签页1,html:标签页1内容}, {title: 标签页2,html:标签页2内容}, {title: 标签页…

【教学类-34-05】拼图(数字学号0X-长方块拼图-双色深灰浅灰)3*3格子(中班主题《个别化拼图》偏艺术-美术)

作品展示 背景需求 难点&#xff1a;如何让生成图片带两个颜色的数字&#xff1f; 上一次学习活动中&#xff0c;发现03、04、05、06、08、09 、 23、26、28拼图都有困境&#xff0c;教师帮助。这些数字都包含圆弧结构&#xff0c;幼儿对于大量的圆弧碎片图形的多重组合&…

【Java-Crawler】一文学会使用WebMagic爬虫框架

WebMagic 爬虫主要分为采集、处理、存储三个部分。 在学 WebMagic 框架之前&#xff0c;需要了解 HttpClient、Jsoup&#xff08;Java HTML Parse&#xff09; 库&#xff0c;或者说会他们的基本使用。因为 WebMagic 框架内部运用了他们&#xff0c;在你出现问题看源码去查错时…

MySQL主从复制配置

一、MySQL主从概念 MysSQL主从复制是一个异步的复制过程&#xff0c;底层是基于Mysql数据库自带的二进制日志功能。就是一台或多台AysQL数据库(slave&#xff0c;即从库&#xff09;从另一台MysQL数据库(master&#xff0c;即主库&#xff09;进行日志的复制然后再解析日志并应…

一、尚医通排班管理

文章目录 一、排班管理1、页面效果2、接口分析3、实现分析 二、排班管理实现1、科室列表1.1 api接口1.1.1 添加service接口与实现1.1.2 添加controller接口 1.2 科室前端1.2.1 添加路由1.2.2 添加按钮1.2.3封装api请求1.2.4 部门展示 2、排班日期分页列表2.1 api接口2.1.1 添加…

Net跨平台UI框架Avalonia入门-DataGrid的使用

Avalonia中的DataGrid的使用 DataGrid 数据表格是客户端UI中很重要的一个控件&#xff0c;Avalonia中的DataGrid是单独一个包Avalonia.Controls.DataGrid&#xff0c;要使用DataGrid&#xff0c;需要另外在Nuget中按这个包&#xff0c;下面介绍一下DataGrid的安装和使用 Data…

android/ios 一键抽取硬编码字符串

由于老项目 做国际化困难,抽取繁琐 最终实在蛋疼 最终开放插件来解决 android studio 插件 一键抽取硬编码字符串 xcode 一键抽取硬编码字符串 环境配置android studio ,appcode idea 环境类似1.安装插件 插件下载点击 2. 配置生成文件路径 android 就是string.xml ios Loca…

搭建stm32电机控制代码框架(一)

也是挑战一下自己吧&#xff0c;看看多久能够把自己的代码框架搭建起来&#xff0c;今天是5月23日&#xff0c;看看最终搭建成功的时候是什么时候&#xff0c;目标其实这个阶段很简单&#xff0c;电机转一个双闭环FOC就行。 这次的任务是基于stm32f405芯片进行展开&#xff0c…

STM32单片机(一)STM32简介

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

国产FPGA选型指南:如何选择适合你的国产FPGA芯片?

FPGA自1985年由Xilinx的创始人之一Ross Freeman发明后&#xff0c;全球90%的FPGA市场一直被国外厂家所占有&#xff0c;主要是这四家公司&#xff1a;Xilinx、Altera、Lattice、Microsemi。 Xilinx 和Altera公司占据了全球近80%以上的市场份额&#xff0c;且拥有着FPGA领域绝大…

Java学习笔记20——内部类

内部类 内部类的访问特点内部类的形式成员内部类局部内部类匿名内部类匿名内部类在开发中使用 内部类是类中的类 内部类的访问特点 1.内部类可以直接访问外部类的成员&#xff0c;包括私有成员 2.外部要访问内部类的成员&#xff0c;必须创建对象 内部类的形式 成员内部类 …

java变量类型

文章目录 一、java变量类型一.java变量类型二、Java 参数变量三、Java 局部变量四、成员变量&#xff08;实例变量&#xff09;五、类变量&#xff08;静态变量&#xff09; 总结 一、java变量类型 一.java变量类型 在Java语言中&#xff0c;所有的变量在使用前必须声明。声明…

13. InnoDB引擎底层原理及Mysql 8.0 新增特性详解

MySQL性能调优 查看系统中的各种LSN值 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 01.redo日志的作用和格式 02.redo日志的写入过程 03.什么是Log Sequence Number? 04.innodb_flush_log_at_trx _c…

(学习日记)AD学习 #3

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

二叉树的学习

最近在复习数据结构和线性代数&#xff0c;先准备周六的线代考试&#xff0c;好好复习。 目录 树的概念 结点&#xff1a; 空树&#xff1a; 子树&#xff1a; 结点的度&#xff1a; 树的度&#xff1a; 层数&#xff1a; 结点的深度&#xff1a; 结点的高度&#xff…