Android多线程编程、异步消息处理机制以及new Handler()被标记为过时的解决办法,解决Handler内存泄漏问题和AsyncTask的基本用法

news2024/12/23 6:29:07

一、Android多线程编程

1、异步消息处理机制

1.1 弱引用

WeakReference(弱引用)是一种在Java中用于管理对象的引用的特殊引用类型。它的作用是在垃圾回收过程中,允许对象在没有强引用指向它时被回收(当一个对象只有弱引用指向它,而没有强引用指向它时,垃圾回收器可能会在下一次垃圾回收时回收该对象,即使系统内存并不紧张。),从而避免潜在的内存泄漏问题。

有些情况下,**我们可能希望对象在没有强引用指向它时能够被垃圾回收,以避免潜在的内存泄漏问题。**例如,考虑以下情况:

  1. 缓存:我们可能使用缓存来存储某些对象,但当这些对象不再被使用时,我们希望它们能够被垃圾回收,释放内存。
  2. 图片加载:在Android开发中,我们经常加载大量的图片,为了避免内存溢出,我们可能希望在图片不再显示时能够及时释放相关的资源。

WeakReference是一种特殊的引用类型,它允许对象在没有强引用指向它时被垃圾回收。

创建弱引用的方法:

Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);

当我们不再需要弱引用所引用的对象时,可以将弱引用设置为null,使得对象变得不再可达,从而在下一次垃圾回收时被回收。

obj = null;

此时,如果没有其他强引用指向obj,它就会成为弱引用所引用的对象,因为没有强引用指向它,垃圾回收器可能会在适当的时候回收这个对象。

常用方法:

  • weakRef.get()

弱引用对象是通过 WeakReference 类来创建的,该类提供了一个 get() 方法,可以获取弱引用所引用的对象。

如果该对象还没有被垃圾回收器回收,那么 get() 方法会返回该对象的引用;但是如果对象已经被垃圾回收器回收,或者在调用 get() 方法的过程中被回收,那么 get() 方法会返回 null

1.2 强引用

当我们在Java中创建对象,并使用变量来引用这些对象时,通常使用的是强引用。强引用使得对象在有至少一个强引用指向它时保持存活状态,即使系统内存紧张,垃圾回收器也不会回收这些对象。

当我们创建一个对象并将其赋值给一个变量时,这个变量就是一个强引用。

只要强引用存在,垃圾回收器就不会回收被引用的对象,即使系统内存紧张也不会回收。只有当没有任何强引用指向一个对象时,垃圾回收器才会在适当的时候回收该对象,释放内存。

Object obj = new Object(); // 这里obj是一个强引用

即使代码中没有使用 obj,只要 obj 这个变量在作用域内且没有被显式释放(比如设置为 null 或跳出作用域),该对象依然不会被垃圾回收。只有当没有任何强引用指向这个对象时,垃圾回收器才有可能回收它。

1.3 使用Handler构建实例

在Android 10以前我们通常使用这样的方法构建Handler

Handler handler = new Handler(){
    public void handlerMessage(Messafe msg){
        
    }
}

但在已经被标记为过时,这个代码编译时会得到一个警告:“ 警告: [deprecation] Handler中的Handler()已过时” 。因为上面的代码存在内存泄露风险,那么怎么泄露内存,原理是什么呢?

当Activity被销毁时,如果其中使用的Handler被持有,并且在外部引用它的对象的生命周期之外保持活动状态,这就可能导致内存泄漏。在这种情况下,Handler所在的外部类可能无法被回收,因为它与Handler存在着相互引用的关系。

为避免内存泄漏,可以采取以下措施:

  • 使用静态内部类或独立类来实现Handler,以避免与外部类的强引用关系。
  • 尽量避免在Handler中持有外部类(Activity等)的引用,或者在不需要使用Handler时及时取消Message和Runnable的发送。

为了防止内存泄露,官方要求将handler声明为静态类,可内存泄露问题解决了,却引来了另一个很棘手的问题,静态类是无法访问外部类成员的,

怎么解决呢?这个问题很简单将外部类对象通过构造函数传递进来就可以了。

如果消息没处理完呢activity被销毁了,那么我们要怎么办?

此时我们就需要使用到弱引用的**.get()**方法,感知activity是否还存在。

public class MainActivity extends AppCompatActivity {

    public static final int UPDATE_TEXT = 1;
    static ActivityMainBinding binding;
    private Handler handler = new MyHandler(Looper.myLooper(),this);

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        //触发按钮修改文本文字
        binding.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);
                    }
                }).start();
            }
        });
    }

    static class MyHandler extends Handler{
        WeakReference<Activity> mainActivityWeakReference;

        //构造函数传入外部的this
        public MyHandler(@NonNull Looper looper, Activity mainActivity) {
            super(looper);
            //构建一个弱引用对象
            mainActivityWeakReference = new WeakReference<Activity>(mainActivity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

            //判断活动是否还存在,如果不存在则结束
            Activity activity = mainActivityWeakReference.get();
            if (activity == null){
                return;
            }

            switch (msg.what){
                case UPDATE_TEXT:
                    binding.Text.setText("Nice to meet too");
            }
        }
    }
}

2、解析异步消息处理机制

Android中的异步消息处理机制主要由四个部分组成:Message、Handler、MessageQueue、Looper

2.1 Message

在线程之间传递消息,用于不同线程只见交换数据,例如上段代码的what用法。

2.2 Handler

Handler也就是处理者,主要用于发送消息和处理消息。发送消息一般使用HandlersetMessage()方法,发送的消息最终会传递到Handler的HanleMessage()方法中,我们通常需要重写HanlerMessage方法。

2.3 MessageQueue

消息队列,通常存放通过Handler发送的所以消息,每个线程中只有一个MessageQueue对象。

2.4 Looper

Looper是每个线程中MessageQueue的管家,调用Looper的loop()方法就可以进入一个无限的循环,每当在MessageQueue中发现一条消息,就会将消息取出发送给Handler的HandleMessage()方法中,同样每个线程中也只有一个Looper对象。

Looper.myLooper()是一个静态方法,用于获取当前线程的Looper对象。

2.5 异步消息处理流程

首先需要在主线程当中创建一个静态继承Handler的内部类,并重写handleMessage()方法。然后创建一个该内部类的对象MyHandler

然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过MyHandler将这条消息发送出去。

之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回内部类的handleMessage()方法中。

由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。

image-20230801114843890

3、使用AsyncTask(谨用)

借助AsyncTask即使不了解异步消息处理机制也可以十分简单的从子线程切换到主线程。

AsyncTask是一个抽象方法我们需要用一个子类继承它,并且使用时需要指定三个泛型参数:

  • **Params:**在执行AsyncTask时需要传入的参数
  • **Progress:**后台任务执行时,如果需要在界面显示当前进度,则使用这里的泛型类型作为进度单位。
  • **Result:**当任务结束后的返回值。

例如:

class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
}
  • 第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传人参数给后台任务。
  • 第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单
    位。
  • 第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果。

接下来需要重写几个方法:

3.1 onPreExecute

这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

3.2 doInBackground()

**这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。**任务一旦完成就可以通过return语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的。

比如说反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成。

3.3 OnProgressUpdate(Progress…)

当在后台任务中调用了publishProgress方法后,onProgressUpdate方法就会很快被调用。

该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

3.4 onPostExecute(Result)

当后台任务结束时并且通过retrun语句返回,这个方法就会被调用。返回的数据会作为参数传递到这个方法,可以根据数据进行UI操作。

3.5 实现自定义AsyncTask

image-20230801162447918

首先使用AlertDialog和ProgressBar实现一个悬浮的加载框。

class MyAskncTask extends AsyncTask<Void,Integer,Boolean> {

    private AlertDialog alertDialog;
    int downloadPercent = 0;

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        MainActivity.this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setView(R.layout.progressbar);
                alertDialog = builder.create();
                alertDialog.setMessage("Downloaded: "+0+"%");
                alertDialog.show();
            }
        });
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        try {
            while (true){
                Thread.currentThread().join(100);
                downloadPercent+= 1;
                //传递下载进度
                publishProgress(downloadPercent);
                if(downloadPercent>100){
                    break;
                }
            }
        }catch (Exception e){
            return false;
        }
        return true;
    }


    @Override
    protected void onPostExecute(Boolean aBoolean) {
        alertDialog.dismiss();
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        alertDialog.setMessage("Downloaded"+values[0]+"%");
    }
}

实现后台运行加载,当再次点击按钮时,进入后台加载的页面:

binding.button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        runOnUiThread(new Runnable() {
            final MyAskncTask myAskncTask = new MyAskncTask(); // 在这里赋值
            @Override
            public void run() {
                //判断是否正在运行
                if ( myAskncTask.getStatus() == AsyncTask.Status.RUNNING) {
                    if (alertDialog != null) {
                        //如果正在运行重新显示弹窗
                        alertDialog.show();
                    }
                } else {
                    myAskncTask.execute();
                }
            }
        });
    }
});
  • PENDING:任务已创建,但尚未执行。
  • RUNNING:任务正在运行,它的 doInBackground() 方法正在执行。
  • FINISHED:任务已完成执行。一旦任务完成运行,它就不能再次执行。

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

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

相关文章

如何防止亚马逊买家号关联?

防止亚马逊买家号关联是指避免在同一家亚马逊账户下使用多个买家号。有时&#xff0c;卖家或买家会创建多个买家号来规避亚马逊的规则和限制。然而&#xff0c;这样的行为违反了亚马逊的政策&#xff0c;并可能导致账户被封禁或其他严重的后果。 而想要防关联&#xff0c;可以从…

【Linux】IO 篇:文件调用原理,文件描述符,FILE的内涵,解析重定向,理解缓冲区

文章目录 一、系统调用接口二、文件调用1. 文件描述符 fd2. 文件调用原理3. FILE 三、重定向dup2 四、缓冲区简易 FILE 的代码实现 文件被加载之前&#xff0c;被存在磁盘上&#xff0c;操作文件&#xff0c;文件的部分内容则会被调度到 内存中。 要分析文件&#xff0c;我们也…

问道管理:沪指震荡跌0.84%,银行、医药等板块走弱,地产板块逆市拉升

2日早盘&#xff0c;沪指盘中震动下探&#xff0c;深成指、创业板指亦走低&#xff1b;两市半日成交约5400亿元&#xff0c;北向资金净卖出约35亿元。 到午间收盘&#xff0c;沪指跌0.84%报3263.2点&#xff0c;深成指跌0.42%&#xff0c;创业板指跌0.27%&#xff0c;上证50指数…

UPnP是什么?有什么更好的连接方案?快解析内网穿透

一、UPnP是什么 有些小伙伴对于UPnP并不了解&#xff0c;其实UPnP只是一种网络协议&#xff0c;主要作用就是简化家庭和企业网络中设备之间的连接和通信过程&#xff0c;它的主要目标是实现网络的无缝连接&#xff0c;并简化相关网络操作。 二、UPnP有什么主要作用&#xff1…

激光切割机好不好?激光切割与线切割应该怎么选择对比

在选择激光切割机与线切割机进行比较时&#xff0c;我们首先需要理解两者的特性与特点。 激光切割机&#xff1a;现阶段主流的激光切割设备主要包括光纤激光切割机和CO2激光切割机。其中CO2激光切割机主要用于切割厚板&#xff0c;但也可完成非金属材料的切割。光纤激光切割机则…

面试题:JS中的String常见方法有哪些?

面试题&#xff1a;说不出五个就尴尬了&#xff01;我目前只写了几个方法&#xff0c;待更新中。。。 1、length2、slice()3、substr()4、substring()5、split()6、indexOf() 1、length 作用&#xff1a;检测字符串的长度。 let str abcde console.log(str.length) // 52、sl…

深入解读Gartner 2023中国数据、分析与AI技术成熟度曲线报告

近日&#xff0c;国际权威研究机构Gartner发布了《Hype Cycle for Data, Analytics and AI in China, 2023》&#xff08;2023中国数据、分析与AI技术成熟度曲线报告&#xff09;。作为业内的权威报告&#xff0c; Gartner 每年针对技术、应用和行业创建的技术成熟度曲线&#…

【WebRTC---源码篇】(二:一)PeerConnection详解

Track的添加 上图是整体流程图 RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack(rtc::scoped_refptr<MediaStreamTrackInterface> track,const std::vector<std::string>& stream_ids) {RTC_DCHECK_RUN_ON(signal…

Linux(三)---------网络路由命令(route路由命令)

一.route路由命令 1.什么是route路由&#xff1f; 计算机之间的数据传输必须经过网络&#xff0c;网络可以直接两台计算机&#xff0c;也可以通过一个一个的节点去连接。路由可以理解为互联网的中转站&#xff0c;网络中的数据包就是通过一个一个的路由器转发到目的地的。 路…

【枚举边+树的直径】CF14D

Problem - 14D - Codeforces 题意&#xff1a; 思路&#xff1a; 两条链不相交&#xff0c;说明是在不同连通分量中&#xff0c;我们可以枚举边来把树分为两个连通分量&#xff0c;然后分别计算直径即可 Code&#xff1a; #include <bits/stdc.h>#define int long lo…

目标检测与跟踪 (1)- 机器人视觉与YOLO V8

目录 1、研究背景 2. 算法原理及对比 2.1 点对特征&#xff08;Point Pairs&#xff09; 2.2 模板匹配 2.3 霍夫森林 2.4 深度学习 3、YOLO家族模型演变 4、YOLO V8 1、研究背景 机器人视觉识别技术是移动机器人平台十分关键的技术&#xff0c;代表着机器人智能化、自动化…

XGBoost的参数空间与超参数优化

目录 1. 确定XGBoost的参数空间 2. 基于TEP对XGBoost进行优化 1. 确定XGBoost的参数空间 对任意集成算法进行超参数优化之前&#xff0c;我们需要明确两个基本事实&#xff1a;①不同参数对算法结果的影响力大小&#xff1b;②确定用于搜索的参数空间。对XGBoost来说&#x…

什么?仅使用配合功能就能让内燃机运动起来

配合作为SOLIDWORKS中装配体的重要命令&#xff0c;拥有着非常强大的功能。根据应用场合的不同软件将其分类为标准配合、高级配合和机械配合&#xff0c;按照这个顺序功能也越来越强大。本次使用内燃机的模型&#xff0c;将三个配合依次进行展示。 标准配合包含了常见的自由度约…

redis安装,开启自启动(Windows)

1、下载redis包 Releases tporadowski/redis GitHub 2、配置环境变量 path中添加redis解压后的路径&#xff1b; 3、配置文件修改 注释掉 bind 127.0.0.1 使其他ip可以远程访问&#xff1b; 修改redis密码 4、启动Redis服务 redis-server.exe --service-install redis.w…

如何让win10开机默认开启小键盘?

一、背景 省流版&#xff1a;直接看最后。 如题。 随便搜一搜&#xff0c;就会发现&#xff0c;有大量雷同&#xff0c;甚至内容完全相同的文章&#xff0c;然而并无卵用。 如&#xff1a; 如何让win10开机默认开启小键盘&#xff1f;【知乎】 如何让win10开机默认开启小键盘…

【网络】网络层(IP协议)

目录 一、基本概念 二、协议头格式 三、网段划分 四、特殊的IP地址 五、IP地址的数量限制 六、私有IP地址和公网IP地址 七、路由 一、基本概念 IP协议&#xff1a;提供一种能力&#xff0c; 将数据从A主机送到B主机&#xff0c;&#xff08;TCP协议&#xff1a;确保IP协议…

python去除重复图片(数据清洗)

其中1文件夹中有重复出现的图片,只是图片名不同。 2文件夹为空文件夹,用于保存去除的重复图。运行py文件。 import shutil import numpy as np from PIL import Image import osdef 比较图片大小(dir_image1, dir_image2):with open(dir_image1, "rb") as f1:size1…

postgresql 查询:查询是否在该列:一个字符串以“|”分割的列

上面是section 表中的link_laneid 的字段描述。 需求是&#xff1a;查询一个值是否在link_laneId 中存在&#xff08;注意这个值是个复合类型&#xff0c;以“|”分割的字符串&#xff09;。 sql语句&#xff1a; select * from section where 162243 any((string_to_arr…

怎样选择适合的爬虫ip服务商?

在当今数字化的时代&#xff0c;越来越多的企业和个人需要采集和分析大量的数据来进行市场调研、竞品分析、舆情监测等工作。而为了保护其数据和资源&#xff0c;很多网站采取了反爬虫措施&#xff0c;限制了普通用户和爬虫程序的访问。为了应对这种限制&#xff0c;许多人开始…

2023年8月5日(星期六):骑行小河边村

2023年8月5日(星期六)&#xff1a;骑行小河边村&#xff0c;早8:30到9:00&#xff0c; 大观公园门囗集合&#xff0c;9:30点准时出发 【因迟到者&#xff0c;骑行速度快者&#xff0c;可自行追赶偶遇。】 偶遇地点: 大观公园门囗集合&#xff0c;家住南&#xff0c;东&#x…