iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >内存泄露导致Android中setVisibility()失效怎么解决
  • 290
分享到

内存泄露导致Android中setVisibility()失效怎么解决

2023-07-02 14:07:19 290人浏览 八月长安
摘要

本篇内容介绍了“内存泄露导致Android中setVisibility()失效怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、前情

本篇内容介绍了“内存泄露导致Android中setVisibility()失效怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    一、前情概要

    目前,我在开发的一个 Android 项目需要各个功能做到线上动态化,其中,App 启动时显示的 Loading 模块,会优先检测加载远程的 Loading 模块,加载失败时,会使用 App 本身默认的 Loading 视图,为此,我编写了一个 LoadingLoader 工具类:

    object LoadingLoader {    private var isInited = false // 防止多次初始化    private lateinit var onLoadFail: () -> Unit // 远程loading加载失败时的回调    private lateinit var onLoadComplete: () -> Unit // 加载完成后回调(无论成功失败)    fun init(onLoadFail: () -> Unit = {}, onLoadComplete: () -> Unit = {}): LoadingLoader {        if (!isInited) {            this.onLoadFail = onLoadFail            this.onLoadComplete = onLoadComplete            isInited = true        } else {            log("you have inited, this time is not valid")        }        return this    }    fun Go() {        if (isInited) {            loadRemoteLoading(callback = { isSuccess ->                if (!isSuccess) onLoadFail()                onLoadComplete()            })        } else {            log("you must invoke init() firstly")        }    }    private fun loadRemoteLoading(callback: (boolean: Boolean) -> Unit) {        // 模拟远程 Loading 模块加载失败        Handler(Looper.getMainLooper()).postDelayed({            callback(false)        }, 1000)    }    private fun log(msg: String) {        Log.e("LoadingUpdater", msg)    }}

    LoadingLoader 工具类使用 Kotlin 的单例模式,init() 方法接收 2 个回调参数,go() 方法触发加载远程 Loading 模块,并根据加载结果执行回调,其中 isInited 用于防止该工具类被初始化多次。然后,在 App 的主入口 LoadingActivity 中使用 LoadingLoader,当加载远程 Loading 模块失败时,将原本隐藏的默认 Loading 视图显示出来;当加载 Loading 模块完成后(无论成功失败),模拟初始化数据并跳转主界面,关闭 LoadingActivity:

    class LoadingActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_loading)        // Loading 模块加载器        LoadingLoader.init(onLoadFail, onLoadComplete).go()    }    override fun onDestroy() {        super.onDestroy()        Log.e("gitLqr", "onDestroy")    }    private val onLoadFail: () -> Unit = {        // 显示默认 loading 界面        findViewById<View>(R.id.cl_def_loading).setVisibility(View.VISIBLE)    }    private val onLoadComplete: () -> Unit = {        // 模拟初始化数据,1秒后跳转主界面        Handler(Looper.getMainLooper()).postDelayed({            val intent = Intent(this, MainActivity::class.java)            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)            // 注意:此处意图使用的 flag,会将 LoadingActivity 界面关闭,触发 onDestroy()            startActivity(intent)        }, 1000)    }}

    LoadingActivity 的 xml 布局代码如下,默认的 Loading 布局初始状态不可见,即 visibility="gone"

    <?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="Http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/black">    <androidx.constraintlayout.widget.ConstraintLayout        android:id="@+id/cl_def_loading"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="#f00"        android:visibility="gone"        tools:visibility="visible">        <TextView            android:layout_width="match_parent"            android:layout_height="match_parent"            android:gravity="center"            android:text="很好看的默认loading界面"            android:textColor="@color/white"            android:textSize="60dp" />        <ProgressBar            android:id="@+id/pb_loading"            android:layout_width="0dp"            android:layout_height="0dp"            android:indeterminateDrawable="@drawable/anim_loading"            app:layout_constraintBottom_toBottomOf="parent"            app:layout_constraintDimensionRatio="1"            app:layout_constraintLeft_toLeftOf="parent"            app:layout_constraintRight_toRightOf="parent"            app:layout_constraintTop_toTopOf="parent"            app:layout_constraintVertical_bias="0.75"            app:layout_constraintWidth_percent="0.064" />    </androidx.constraintlayout.widget.ConstraintLayout></FrameLayout>

    这里会发现一个问题,因为是以清空栈的方式启动 MainActivity,所以第二次启动时,理论上应该会跟第一次启动时界面显示效果完全一致,即每次启动都会显示默认的 Loading 视图,但是实际情况并没有,而控制台的日志也证实了 LoadingActivity 的 onDestroy() 有被触发:

    内存泄露导致Android中setVisibility()失效怎么解决

    二、摸索过程

    1、代码执行了吗?

    难道第二次启动 App 时,LoadingActivity.onLoadFail 没有触发吗?加上日志验证一下:

    class LoadingActivity : AppCompatActivity() {    ...    private val onLoadFail: () -> Unit = {        // 显示默认 loading 界面        val defLoading = findViewById<View>(R.id.cl_def_loading)        defLoading.setVisibility(View.VISIBLE)        Log.e("GitLqr", "defLoading.setVisibility --> ${defLoading.visibility}")    }}

    重新打包再执行一遍上面的演示操作,日志输出如下:

    内存泄露导致Android中setVisibility()失效怎么解决

    说明 2 次启动都是有触发 LoadingActivity.onLoadFail 的,并且结果都是 0 ,即 View.VISIBLE。

    2、视图不显示的直接原因是什么?

    既然,代码有输出日志,那说明 setVisibility(View.VISIBLE) 这行代码肯定执行过了,而界面上不显示,直接原因是什么?是因为默认 Loading 视图的 visibility 依旧为 View.GONE?又或者是因为其他因素导致 View 的尺寸出现了问题?这时,可以使用 AndroidStudio 的 Layout Inspector 工具,可以直观的分析界面的布局情况,为了方便 Layout Inspector 工具获取 LoadingActivity 的布局信息,需要将 LoadingActivity.onLoadComplete 中跳转主界面的代码注释掉,其他保持不变:

    class LoadingActivity : AppCompatActivity() {    ...    private val onLoadComplete: () -> Unit = {        // 模拟初始化数据,1秒后跳转主界面        Handler(Looper.getMainLooper()).postDelayed({//            val intent = Intent(this, MainActivity::class.java)//            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)//            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)//            // 注意:此处意图的 flag,会将 LoadingActivity 界面关闭,触发 onDestroy()//            startActivity(intent)        }, 1000)    }}

    然后重复上述演示操作,第一次启动,显示出默认 Loading,手动按返回键退出 App,再第二次启动,不显示默认 Loading。

    控制台日志信息也如期输出,第二次启动确实执行了 setVisibility(View.VISIBLE)

    内存泄露导致Android中setVisibility()失效怎么解决

    这时,使用 Layout Inspector(菜单栏 -> Tools -> Layout Inspector),获取到 LoadingActivity 的布局信息:

    内存泄露导致Android中setVisibility()失效怎么解决

    这里可以断定,就是默认 Loading 视图的 visibility 依旧为 View.GONE 的情况。

    注:因为 View.GONE 不占据屏幕空间,所以宽高都为 0,是正常的。

    3、操作的视图是同一个吗?

    现在回顾一下上述的 2 个线索,首先,代码中确定执行了 setVisibility(View.VISIBLE),并且日志里也显示了该视图的显示状态为 0,即 View.VISIBLE:

    内存泄露导致Android中setVisibility()失效怎么解决

    其次,使用 Layout Inspector 看到的的视图状态却为 View.GONE:

    内存泄露导致Android中setVisibility()失效怎么解决

    所以,真相只有一个,日志输出的视图 和 Layout Inspector 看到的的视图,肯定不是同一个!!为了验证这一点,代码再做如下调整,分别在 onCreate() 和 onLoadFail 中打印默认 Loading 视图信息:

    class LoadingActivity : AppCompatActivity() {    ...    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_loading)        val defLoading = findViewById<View>(R.id.cl_def_loading)        Log.e("GitLqr", "onCreate ---> view is ${defLoading}")        // Loading 模块加载器        LoadingLoader.init(onLoadFail, onLoadComplete).go()    }    private val onLoadFail: () -> Unit = {        // 显示默认 loading 界面        val defLoading = findViewById<View>(R.id.cl_def_loading)        defLoading.setVisibility(View.VISIBLE)        Log.e("GitLqr", "defLoading.setVisibility --> ${defLoading.visibility}, view is ${defLoading}")    }}

    再如上述演示操作一遍,日志输出如下:

    内存泄露导致Android中setVisibility()失效怎么解决

    可以看到第二次启动时,LoadingActivity.onLoadFail 中操作的视图,还是第一次启动时的那个视图,该视图是通过 findViewById 获取到的,说明 LoadingActivity.onLoadFail 中引用的 Activity 是第一次启动时的 LoadingActivity,也就是说 LoadingActivity 发生内存泄露了。此时才焕然大悟,Kotlin 中的 Lambda 表达式(像 onLoadFail、onLoadComplete 这种),对应到 Java 中就是匿名内部类,通过 Kotlin Bytecode 再反编译成 java 代码可以验证这点:

    public final class LoadingActivity extends AppCompatActivity {   private final Function0 onLoadFail = (Function0)(new Function0() {      // $FF: synthetic method      // $FF: bridge method      public Object invoke() {         this.invoke();         return Unit.INSTANCE;      }      public final void invoke() {         View defLoading = LoadingActivity.this.findViewById(1000000);         defLoading.setVisibility(0);         StringBuilder var10001 = (new StringBuilder()).append("defLoading.setVisibility --> ");         Intrinsics.checkExpressionValueIsNotNull(defLoading, "defLoading");         Log.e("GitLqr", var10001.append(defLoading.getVisibility()).append(", view is ").append(defLoading).toString());      }   });   private final Function0 onLoadComplete;   protected void onCreate(@Nullable Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      this.setContentView(1300004);      View defLoading = this.findViewById(1000000);      Log.e("GitLqr", "onCreate ---> view is " + defLoading);      LoadingLoader.INSTANCE.init(this.onLoadFail, this.onLoadComplete).go();   }   protected void onDestroy() {      super.onDestroy();      Log.e("GitLqr", "onDestroy");   }   public LoadingActivity() {      this.onLoadComplete = (Function0)null.INSTANCE;   }}

    我们知道,Java 中,匿名内部类会持有外部类的引用,即匿名内部类实例 onLoadFail 持有 LoadingActivity 实例,而 onLoadFail 又会通过 LoadingLoader.init() 方法传递给 LoadingLoader 这个单例对象,所以间接导致 LoadingLoader 持有了 LoadingActivity,因为单例生命周期与整个 App 进程相同,所以只要 App 进程不死,内存中就只有一分 LoadingLoader 实例,又因为是强引用,所以 GC 无法回收掉第一次初始化时传递给 LoadingLoader 的 LoadingActivity 实例,所以,无论重启多少次,onLoadFail 中永远都是拿着第一次启动时的 LoadingActivity 来执行 findViewById,拿到的 Loading 视图自然也不会是当前最新 LoadingActivity 的 Loading 视图。

    三、解决方案

    既然知道是因为 LoadingActivity 内存泄露导致的,那么解决方案也简单,就是在 LoadingLoader 完成它的使命之后,及时释放掉对 LoadingActivity 的引用即可,又因为 LoadingActivity 实际上并不是被 LoadingLoader 直接引用,而是被其内部变量 onLoadFail 直接引用的,那么在 LoadingLoader 中只需要将 onLoadFail 的引用切断就行了:

    object LoadingLoader {    private var isInited = false // 防止多次初始化    private lateinit var onLoadFail: () -> Unit // 远程loading加载失败时的回调    private lateinit var onLoadComplete: () -> Unit // 加载完成后回调    fun go() {        if (isInited) {            loadRemoteLoading(callback = { isSuccess ->                if (!isSuccess) onLoadFail()                onLoadComplete()                destroy() // 使命完成,释放资源            })        } else {            log("you must invoke init() firstly")        }    }    fun destroy() {        this.onLoadFail = {}        this.onLoadComplete = {}        this.isInited = false    }}

    “内存泄露导致Android中setVisibility()失效怎么解决”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

    --结束END--

    本文标题: 内存泄露导致Android中setVisibility()失效怎么解决

    本文链接: https://www.lsjlt.com/news/342593.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • 内存泄露导致Android中setVisibility()失效怎么解决
      本篇内容介绍了“内存泄露导致Android中setVisibility()失效怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、前情...
      99+
      2023-07-02
    • 内存泄露导致Android 中setVisibility() 失效原理
      目录一、前情概要二、摸索过程1、代码执行了吗?2、视图不显示的直接原因是什么?3、操作的视图是同一个吗?三、解决方案一、前情概要 目前,我在开发的一个 Android 项目需要各个功...
      99+
      2024-04-02
    • Android内存泄露怎么解决
      解决Android内存泄露问题的方法有以下几种:1. 避免长生命周期的引用:确保在不使用时及时释放对象的引用,如Activity中的...
      99+
      2023-09-29
      android
    • 为什么Handle可能导致内存泄露
      一、未正确释放资源 当使用Handle时,必须确保在不再需要资源时正确释放它。如果没有正确释放Handle,资源将一直被占用,导致内存泄漏。例如,如果在打开文件后忘记关闭文件句柄,这将导致文件资源无法释放。 二、引用计数错误 某些编程语言使...
      99+
      2023-10-29
      内存 Handle
    • android内存泄露怎么查看和解决
      Android内存泄露是指内存中的对象无法被及时释放,导致内存占用过高,影响应用的性能和稳定性。以下是查看和解决Android内存泄...
      99+
      2024-03-13
      android
    • c++循环引用导致的内存泄露如何解决
      在 C++ 中,循环引用(circular reference)是指两个或多个对象相互引用,导致内存泄漏的情况。解决循环引用导致的内...
      99+
      2023-10-10
      c++
    • Go 中 time.After 可能导致的内存泄露问题解析
      目录一、Time 包中定时器函数定时函数:NewTicker,NewTimer 和 time.After 介绍二、time.After 导致的内存泄露基本用法有问题代码用pprof分...
      99+
      2023-05-18
      go time.After 内存泄露 time.After 内存泄露 go time.After 
    • qt程序内存泄露怎么解决
      解决Qt程序内存泄漏的方法如下:1. 使用对象的父子关系:在创建对象时,将对象的父对象设置为合适的父对象。这样,当父对象被销毁时,它...
      99+
      2023-08-18
      qt
    • Android中怎么利用Handler防止内存泄露
      今天就跟大家聊聊有关Android中怎么利用Handler防止内存泄露,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。 Handler可能导致的内存泄露及其优化 &...
      99+
      2023-05-30
      android handler
    • Python内存泄露怎么查看和解决
      在Python中,内存泄露指的是由于对象在不再需要时没有被正确释放,导致内存占用不断增加的情况。下面是一些查找和解决Python内存...
      99+
      2023-10-22
      Python
    • ThreadLocal在Tomcat中引起内存泄露怎么解决
      这篇文章主要介绍“ThreadLocal在Tomcat中引起内存泄露怎么解决”,在日常操作中,相信很多人在ThreadLocal在Tomcat中引起内存泄露怎么解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答...
      99+
      2023-06-05
    • 怎么在JVM中使用JFR解决内存泄露
      今天就跟大家聊聊有关怎么在JVM中使用JFR解决内存泄露,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一个内存泄露的例子我们举一个内存泄露的例子,先定义一个大对象:public&nb...
      99+
      2023-06-15
    • Android中使用Handler造成的内存泄露如何解决
      这篇文章将为大家详细讲解有关Android中使用Handler造成的内存泄露如何解决,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。一、什么是内存泄露?  Java使用有向图机制,通过GC自动...
      99+
      2023-05-30
      android handler
    • Android中使用webview时出现内存泄露如何解决
      Android中使用webview时出现内存泄露如何解决,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1.避免在xml直接写webview控件,这样会引用activity,...
      99+
      2023-05-31
      android webview
    • Java 中出现内存泄露如何解决
      这期内容当中小编将会给大家带来有关Java 中出现内存泄露如何解决,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。首先,我用下面的命令监视进程:while ( sleep 1&...
      99+
      2023-06-17
    • 分析Android常见的内存泄露和解决方案
      目录一、前言二、Android 内存泄露场景2.1、非静态内部类的静态实例2.2、多线程相关的匿名内部类/非静态内部类2.3、Handler 内存泄露2.4、静态 Activity ...
      99+
      2024-04-02
    • Flex中出现内存泄露如何解决
      Flex中出现内存泄露如何解决,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Flex内存泄露举例:引用泄露:对子对象的引用,外部对本对象或子对象的引用都需要置null;系统类泄...
      99+
      2023-06-17
    • android handler内存泄漏怎么解决
      在Android中,Handler的使用很容易引发内存泄漏问题。以下是一些解决内存泄漏的方法:1. 使用静态内部类:将Handler...
      99+
      2023-09-15
      android
    • Java中的内存泄露问题和解决办法
      目录为什么会产生内存泄漏?内存泄漏对程序的影响?如何检查和分析内存泄漏?常见的内存泄漏及解决方法1、单例造成的内存泄漏2、非静态内部类创建静态实例造成的内存泄漏【已无】3、Handl...
      99+
      2024-04-02
    • Java中什么情况会导致内存泄漏
      这篇文章主要讲解了“Java中什么情况会导致内存泄漏”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java中什么情况会导致内存泄漏”吧!概念内存泄露:指程序中动态分配内存给一些临时对象,但对...
      99+
      2023-06-16
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作