()自定义DialogFragment以及解决其内存泄漏问题

这篇具有很好参考价值的文章主要介绍了()自定义DialogFragment以及解决其内存泄漏问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

日常开发中,dialog是常见的功能,我们时常需要弹出来一些弹框提示用户
今天就定义了一个方便的dialog基类BaseSimpleDialogFragment,
支持快速地显示一个dialog
主要功能有:
initAnimation:设置入场和出场动画
getGravity:设置dialog显示位置(屏幕上,中,下)
getCanceledOnTouchOutside:点击空白处关闭
getWindowWidth
getWindowHeight
getPaddingLeft:动态设置宽高和间距
整体来说比较简单,也方便扩展
创建dialog的时候只需要实现BaseSimpleDialogFragment即可
比如这样:

class MyDialog: BaseSimpleDialogFragment()  {

  override val layoutId: Int = R.layout.dialog_my_show

  companion object {
    @JvmStatic
    fun newInstance(): MyDialog {
      val dialog = MyDialog()
      dialog.arguments = Bundle().apply {
//      putParcelableArrayList(DATA, data)
      }
      return dialog
    }
  }


  override fun initData() {
//    data = arguments?.getParcelableArrayList(DATA) ?: return
  }

  override fun initView(view: View) {
    layoutView.findViewById<Button>(R.id.cancle).setOnClickListener {
      dismissAllowingStateLoss()
    }
  }
}

展示的时候几行代码就可以了:

      MyDialog.newInstance().apply {
        //传递数据
      }.show(supportFragmentManager)

源码

源码这边先贴出来:

abstract class BaseSimpleDialogFragment : DialogFragment() {

  abstract val layoutId: Int

  protected open fun initView(view: View) {
    //sonar
  }

  protected open fun initData() {
    //sonar
  }

  protected open fun initListener() {
    //sonar
  }

  lateinit var layoutView: View

  protected lateinit var mContext: Context


  override fun onAttach(context: Context) {
    super.onAttach(context)
    mContext = context
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    initAnimation()
  }


  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View? {
    layoutView = inflater.inflate(layoutId, container, false)
    return layoutView
  }

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    initView(view)
    initListener()
    initData()
  }


  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val dialog = super.onCreateDialog(savedInstanceState).apply {
      window?.run {
        decorView.setPadding(
          getPaddingLeft(),
          getPaddingTop(),
          getPaddingRight(),
          getPaddingBottom()
        )
        val wlp = attributes.apply {
          gravity = getGravity()
          width = getWindowWidth()
          height = getWindowHeight()
        }
        attributes = wlp
        setWindowParam(this)
      }

      setCanceledOnTouchOutside(getCanceledOnTouchOutside())
    }

    isCancelable = getCancelable()
    return dialog
  }

  protected open fun setWindowParam(window: Window) {
    //sonar 
  }

  protected open fun initAnimation() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      setStyle(STYLE_NORMAL, R.style.FragmentDialogStyleWithAni)
    } else {
      setStyle(STYLE_NORMAL, R.style.FragmentDialogStyle_Low_Level_WithAni)
    }
  }

  protected open fun getGravity(): Int {
    return Gravity.CENTER
  }

  protected open fun getWindowWidth(): Int {
    return WindowManager.LayoutParams.MATCH_PARENT
  }

  protected open fun getWindowHeight(): Int {
    return WindowManager.LayoutParams.WRAP_CONTENT
  }

  protected open fun getPaddingLeft(): Int {
    return 0
  }

  protected open fun getPaddingRight(): Int {
    return 0
  }

  protected open fun getPaddingTop(): Int {
    return 0
  }

  protected open fun getPaddingBottom(): Int {
    return 0
  }

  protected open fun getCanceledOnTouchOutside(): Boolean {
    return false
  }

  protected open fun getCancelable(): Boolean {
    return true
  }

  override fun dismiss() {
    dismissAllowingStateLoss()
  }

  open fun show(manager: FragmentManager) {
    show(manager, javaClass.simpleName)
  }


  override fun show(manager: FragmentManager, tag: String?) {
    try {
      super.show(manager, tag)
    } catch (e: Exception) {
      Log.e("print", "show: $e")
    }
  }

}

用到的style:


  <style name="FragmentDialogStyleWithAni" parent="FragmentDialogStyle">
    <item name="android:windowAnimationStyle">@style/DialogAnimation</item>
  </style>


  <style name="FragmentDialogStyle_Low_Level_WithAni" parent="FragmentDialogStyle_Low_Level">
    <item name="android:windowAnimationStyle">@style/DialogAnimation</item>
  </style>

  <style name="DialogAnimation" parent="@android:style/Animation.Dialog">
    <item name="android:windowEnterAnimation">@anim/push_ani_up_in</item>
    <item name="android:windowExitAnimation">@anim/push_ani_down_out</item>
  </style>

  <style name="FragmentDialogStyle_Low_Level" parent="android:Theme.Holo.Light.Dialog">
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowFrame">@null</item>
    <item name="android:backgroundDimEnabled">true</item>
    <item name="android:windowIsTranslucent">false</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowContentOverlay">@null</item>
  </style>

  <style name="FragmentDialogStyle" parent="Base.AlertDialog.AppCompat.Light">
    <!--点击窗口外是否消失-->
    <item name="android:windowCloseOnTouchOutside">true</item>
    <!-- 背景颜色及透明程度 -->
    <item name="android:windowBackground">@android:color/transparent</item>

    <!-- 是否半透明 -->
    <item name="android:windowIsTranslucent">false</item>
    <!-- 是否没有标题 -->
    <item name="android:windowNoTitle">true</item>
    <!-- 是否浮现在activity之上 设置成false则match_parent可以全屏-->
    <item name="android:windowIsFloating">true</item>
  </style>

还有两个默认的进入和退出动画

<?xml version="1.0" encoding="UTF-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
  <translate
    android:duration="500"
    android:fromYDelta="0"
    android:toYDelta="100%p" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
  <translate
    android:duration="500"
    android:fromYDelta="100%p"
    android:toYDelta="0" />
</set>

内存泄漏

在使用这个自定义的dialog的时候
我发现退出页面时
LeakCanary 会在dialog dismiss后报内存泄漏
大概像这样:
(原创)自定义DialogFragment以及解决其内存泄漏问题,Android开发,android
代码很简单,贴出来:

class MainActivity : AppCompatActivity() {

  lateinit var mydialog: MyDialog
  
  @SuppressLint("MissingInflatedId")
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    findViewById<Button>(R.id.showbtn).setOnClickListener {
      mydialog = MyDialog.newInstance().apply {
        //传递数据
      }
      mydialog.show(supportFragmentManager)
    }
  }
}

dialog内部有按钮,点击就关闭dialog:

    layoutView.findViewById<Button>(R.id.cancle).setOnClickListener {
      dismissAllowingStateLoss()
    }

不知道大家看出来原因没有
看LeakCanary 日志,告诉我的是dialogFragment 收到了onDestroy的回调了。
也就是被销毁,那么gc就应该回收掉该fragment对象。
但是呢当前界面还持有该对象的引用造成了内存泄漏。

我们点进dismissAllowingStateLoss的源码
(原创)自定义DialogFragment以及解决其内存泄漏问题,Android开发,android
可以看到:dismissAllowingStateLoss应该是要去remove这个fragment,
但是如果activity持有的话,就无法被内存回收了,从而导致了内存泄漏

解决

解决办法也很简单,提供几种方法:
1:简单粗暴,dismiss的时候,把Activity的引用置位null
首先我们的kotlin代码就要改下:

  var mydialog: MyDialog?=null

然后dismiss的时候,把Activity的引用置位null

    mydialog=null
    mydialog?.dismiss()

2:创建一个一次性的对象来使用,也就是局部变量,让当前界面不再全局持有该dialog对象。

    findViewById<Button>(R.id.showbtn).setOnClickListener {
      var mydialog = MyDialog.newInstance().apply {
        //传递数据
      }
      mydialog.show(supportFragmentManager)
    }

3:弱引用dialog,利用弱引用的特性,确保内存可以顺利回收

class MainActivity : AppCompatActivity() {

  lateinit var mydialog: WeakReference<MyDialog>

  @SuppressLint("MissingInflatedId")
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    findViewById<Button>(R.id.showbtn).setOnClickListener {
      mydialog = WeakReference(MyDialog.newInstance().apply {
        //传递数据
      })
      mydialog.get()?.show(supportFragmentManager)
    }

  }
}

关于自定义DialogFragment解决内存泄漏的问题,大体就是这些办法
下面讲一下DialogFragment可能存在的一些坑

踩坑

修改dialogFragment的背景色

在onCreateView方法里通过这种方式来修改背景色

    //设置dialog背景色为透明色
    dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
    //设置dialog窗体颜色透明
    dialog?.window?.setDimAmount(0f)

解决打开软键盘,dialogFragment上移问题

<!--这个属性设置为false可以防止输入法把弹窗顶上去-->
 <item name="android:windowIsFloating">false</item>

打开dialogFragment,退出到后台或者打开新页面,然后再返回的时候,dialogFragment会重新执行动画

问题原因:当dialogFragment不可见的时候,会重新执行动画
解决办法:
1、在onCreateView中给windown添加转场属性。这样DialogFragment就具有了转场动画效果。
2、在onStop中取消掉转场动画,这样DialogFragment就不再有转场动画效果。此时跳转到其他页面,在回到当前dialogfragment,由于Dialogfragment动画被取消,所以不会再次执行进场动画。
3、在onResume再次为DialogFragment设置转场动画。注意这里需要使用handler延时,因为Activity是在onResume执行之后,才将自身所在的Window添加到WindowManager中的,然后才会调用ViewRootImpl的setview方法才开始View绘制的,如果不使用延时,相当于此时又给DialogFragment设置了转场动画效果。那么我们在步骤2中onStop取消动画就没有意义了。因为从其他页面回到DialogFragment执行onResume后再去绘制页面的,此时如果直接在onResume设置DialogFragment的动画,那DialogFragment实际上就具有了转场动画属性,还是会再执行一次进场动画。所以这里使用一个handler延时来规避这个时间差。(在DialogFragment渲染完成后再设置DialogFragment的转场动画,就不会对步骤2造成干扰)。此时DialogFragment具有了转场动画,那么我们结束DialogFragment时,就会有退出动画了,刚好弥补掉注意点1。
大体修改代码如下:


 private val orientation: Int = R.style.BottomAnimBottom //弹出的动画

 override fun onStop() {
        super.onStop()
        if (dialog != null && dialog?.window != null) {
            dialog?.window?.setWindowAnimations(0)
        }
    }
private val handler: Handler = Handler()
    override fun onResume() {
        super.onResume()
        handler.postDelayed({
            if (dialog != null && dialog?.window != null) {
                dialog?.window?.setWindowAnimations(orientation)
            }
        },500)
    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
    }
 
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
   
        if (dialog != null && dialog?.window != null) {
            dialog?.window?.setWindowAnimations(orientation)
        }
        return view
    }

我们上面的例子因为用的是setStyle的方法在Style中来设置了动画效果
windowAnimationStyle属性设置为DialogAnimation
所以没有在onCreateView调用setWindowAnimations方法
但其他地方的修改是一样的,这里提供一份修改后的代码:

abstract class BaseSimpleAnimDialogFragment : BaseSimpleDialogFragment() {

  private var dialogAnimation: Int = R.style.DialogAnimation //弹出的动画

  private val handler: Handler = Handler()

  /**
   * 如果重写了initAnimation方法,也需要重写这个方法去设置入场动画
   * initAnimation:用来设置弹框样式,包括了进入动画
   * getdialogAnimation:只用来设置进入动画
   *
   * 所以如果设置的新样式里,默认动画已经改了,那么也要重写getdialogAnimation方法去修改动画
   * 这样才能确保onResume方法执行时,设置的动画和样式里的动画一致
   */
  protected open fun getdialogAnimation():Int {
      return R.style.DialogAnimation
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    dialogAnimation = getdialogAnimation()
  }


  override fun onResume() {
    super.onResume()
    handler.postDelayed({
      if (dialog != null && dialog?.window != null) {
        dialog?.window?.setWindowAnimations(dialogAnimation)
      }
    },500)
  }

  override fun onStop() {
    super.onStop()
    if (dialog != null && dialog?.window != null) {
      dialog?.window?.setWindowAnimations(0)
    }
  }
  override fun onDestroy() {
    super.onDestroy()
    handler.removeCallbacksAndMessages(null)
  }

}

子类需要重写initAnimation和getdialogAnimation方法来修改动画
确保initAnimation方法里面设置的Style的windowAnimationStyle属性是和getdialogAnimation返回的属性Style一致的即可

Fragment恢复时注意事项:InstantiationException

这个问题可以参考这篇博客:
Fragment恢复时注意事项:InstantiationException,别在Fragment写有参数的构造方法
主要原因就是Fragment的构造方法内有参数需要传递
把传参的行为抽出来一个方法给外部去主动调用即可文章来源地址https://www.toymoban.com/news/detail-608117.html

到了这里,关于()自定义DialogFragment以及解决其内存泄漏问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • centos下使用jemalloc解决Mysql内存泄漏问题

    参考: MySQL bug:https://bugs.mysql.com/bug.php?id=83047tdsourcetag=s_pcqq_aiomsg  https://github.com/jemalloc/jemalloc/blob/dev/INSTALL.md (1)ptmalloc 是glibc的内存分配管理 (2)tcmalloc 是google的内存分配管理模块 (3)jemalloc 是BSD的提供的内存分配管理 (可以 使用jemalloc优化Nginx ) 三者jemalloc和tcmall

    2024年02月11日
    浏览(35)
  • Qt中postevent造成内存泄漏问题的通用解决方案

    在Qt中由QCoreApplication统一管理Qt事件的收发和销毁,其中sendEvent为阻塞式发送,用于单线程的事件发送;postevent为非阻塞式发送,构造事件的线程和接受事件的线程可以为两个线程。 最近在做一个个人项目ShaderLab 需要绘制OpenGL实时渲染的图像,由于OpenGL渲染基本都放在循环语

    2024年02月15日
    浏览(30)
  • Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?

     进程等待通常是指: 父进程通过wait()/waitpid()的方式,让父进程对子进程进行资源回收的等待过程!!  进程等待通常是为了解决以下两种情况: 解决子进程僵尸所带来的内存泄漏问题,对僵尸子进程进行资源回收! 原因在于当子进程僵尸后,便“刀枪不入”了。即使是

    2024年04月16日
    浏览(37)
  • 内存泄漏问题

            内存泄漏是一种常见的问题,它可能导致系统内存不断增加,最终耗尽可用内存。解决内存泄漏问题通常需要进行调试和分析。下面是一些可能有助于解决内存泄漏问题的步骤: 1. 监控内存使用情况: a. 使用 malloc 记录日志: 在内存分配的地方添加记录,以便跟

    2024年01月17日
    浏览(30)
  • ThreadLocal有内存泄漏问题吗

    对于ThreadLocal的原理不了解或者连Java中的引用类型都不了解的可以看一下我的之前的一篇文章Java中的引用和ThreadLocal_鱼跃鹰飞的博客-CSDN博客 我这里也简单总结一下: 1. 每个Thread里都存储着一个成员变量,ThreadLocalMap 2. ThreadLocal本身不存储数据,像是一个工具类,基于ThreadL

    2024年02月14日
    浏览(34)
  • 【C++】深入探讨内存管理:malloc/free与new/delete的区别以及如何避免内存泄漏

    在软件开发中,正确处理内存管理是至关重要的一环。在C++编程中,我们经常会用到动态内存管理的工具,比如 malloc/free 和 new/delete 。本文将深入探讨 malloc/free 与 new/delete 之间的区别,以及如何有效地避免内存泄漏问题。 都是用于从堆上申请空间,并需要手动释放。 mallo

    2024年02月22日
    浏览(35)
  • uniapp开发微信小程序解决上线:审核问题“包含明文的AppSecret,存在泄漏的安全风险“以及上线之后接口请求失败

    解决: 原因是我在登录通过uni.login获取到code然后用code获取openid时用到了AppSecret,因为之前后端没空写接口我就在前端自己获取的openid, 解决方案:小程序秘钥放前端审核不通过说不安全,那我就让后端写个接口返回给我,然后仔细检查下代码中是否用到了AppSecret,去掉AppS

    2024年02月11日
    浏览(37)
  • C++ map clear内存泄漏问题

    map自带的clear()函数会清空map里存储的所有内容,但如果map值存储的是指针,则里面的值不会被清空,会造成内存泄漏,所以值为指针的map必须用迭代器清空。 使用erase迭代删除 迭代器删除值为指针的map,一定要注意迭代器使用正确,一旦迭代器失效程序就会崩溃。 调用cle

    2024年02月09日
    浏览(27)
  • iOS解决内存泄漏工具

    在iOS应用中,有一些工具和技术可以帮助识别和解决内存泄漏问题。以下是一些常用的工具,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1.Instruments: Instruments 是Xcode中的性能分析工具,其中包含了用于检测内存泄漏的工具

    2024年01月18日
    浏览(31)
  • 解决鸿蒙APP的内存泄漏

    解决鸿蒙(HarmonyOS)应用的内存泄漏问题需要采用一系列的策略和技术。与解决Android内存泄漏类似,以下是一些建议,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1.使用鸿蒙开发者工具: 鸿蒙提供了开发者工具,其中包括

    2024年02月01日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包