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

news/2024/10/23 3:40:38/

前言

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

class MyDialog: BaseSimpleDialogFragment()  {override val layoutId: Int = R.layout.dialog_my_showcompanion object {@JvmStaticfun 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: Intprotected open fun initView(view: View) {//sonar}protected open fun initData() {//sonar}protected open fun initListener() {//sonar}lateinit var layoutView: Viewprotected lateinit var mContext: Contextoverride 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 = wlpsetWindowParam(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"><translateandroid: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"><translateandroid:duration="500"android:fromYDelta="100%p"android:toYDelta="0" />
</set>

内存泄漏

在使用这个自定义的dialog的时候
我发现退出页面时
LeakCanary 会在dialog dismiss后报内存泄漏
大概像这样:
在这里插入图片描述
代码很简单,贴出来:

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的源码
在这里插入图片描述
可以看到:dismissAllowingStateLoss应该是要去remove这个fragment,
但是如果activity持有的话,就无法被内存回收了,从而导致了内存泄漏

解决

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

  var mydialog: MyDialog?=null

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

    mydialog=nullmydialog?.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以及解决内存泄漏的问题,就介绍到这了。


http://www.ppmy.cn/news/982093.html

相关文章

汇编调用C语言定义的全局变量

在threadx移植中&#xff0c;系统的systick通过了宏定义的方式定义&#xff0c;很难对接库函数的时钟频率&#xff0c;不太利于进行维护 所以在C文件中自己定义了一个systick_Div的变量&#xff0c;通过宏定义方式设定systick的时钟频率 在汇编下要加载这个systick分频系数 …

Spring AOP 中,切点有多少种定义方式?

在 Spring AOP 中&#xff0c;我们最常用的切点定义方式主要是两种&#xff1a; 使用 execution 进行无侵入拦截。使用注解进行拦截。 这应该是是小伙伴们日常工作中使用最多的两种切点定义方式了。但是除了这两种还有没有其他的呢&#xff1f;今天松哥就来和大家聊一聊这个话…

KBYCMS模板文件创建操作说明

创建模板 首先安装“一键生成应用”插件,来帮助我们快速的创建插件。 操作说明 1.安装 2.进入生成界面,填写模板信息,点击提交 3. 生成效果 静态资源目录 当然你也可以手动创建 模板目录 响应式主题 template 模板安装目录 ├─admin 后台模板目录 ├─index 前台…

如何正确使用npm常用命令

npm常用命令&#xff1a; 官方文档&#xff1a;CLI Commands | npm Docs 1. npm -v&#xff1a;查看 npm 版本 2. npm init&#xff1a;初始化后会出现一个 Package.json 配置文件&#xff0c;可以在后面加上 -y&#xff0c;快速跳到问答界面 3. npm install&#xff1a;会…

自定义数据类型

一、结构体的定义与使用 1. 定义结构体类型 结构体允许将不同类型的数据元素组合在一起形成一种新的数据类型 结构体类型声明一般放在程序文件开头&#xff0c;此时这个声明是全局的。 结构体类型声明也可以放到函数内部&#xff0c;此时这个声明是局部的。 &#xff08;1&…

【MySQL】SQL优化(九)

&#x1f697;MySQL学习第九站~ &#x1f6a9;本文已收录至专栏&#xff1a;MySQL通关路 ❤️文末附全文思维导图&#xff0c;感谢各位点赞收藏支持~ 一.插入数据 (1) 小规模数据 如果我们需要一次性往数据库表中插入多条记录: -- 例如我们需要插入大量数据 insert into t…

Linux--进程池

1.一个父进程生成五个子进程且分别建立与子进程管道 ①用for循环&#xff0c;结束条件为<5 ②father父进程每次都要离开for循环&#xff0c;生成下一个子进程和管道 2.#include <cassert>和#include <assert.h>的区别 assert.h 是 C 标准库的头文件&#xff…

gin框架内容(二)

上一篇过于gin的内容 https://mp.csdn.net/mp_blog/creation/editor/131953861 CSDNhttps://mp.csdn.net/mp_blog/creation/editor/131953861 一、路由组 为了管理具有相同前缀的URL, 将拥有URL共同前缀的路由划分为一组 为了代码的阅读性&#xff0c;使用{}包裹相同组的路由…