美文网首页
关于Android VideoView导致的内存泄漏的问题

关于Android VideoView导致的内存泄漏的问题

作者: 程序员大耳 | 来源:发表于2021-09-27 10:38 被阅读0次

泄露信息如下:

====================================

    HEAP ANALYSIS RESULT

    ====================================

    1 APPLICATION LEAKS

    References underlined with "~~~" are likely causes.

    Learn more at https://squ.re/leaks.

    Signature: 4f5c3ad956b290a0c61f4567bfd87dd73f3578

    ┬───

    │ GC Root: Global variable in native code

    │

    ├─ android.media.PlayerBase$1 instance

    │    Leaking: UNKNOWN

    │    Anonymous subclass of com.android.internal.app.IAppOpsCallback$Stub

    │    ↓ PlayerBase$1.this$0

    │                  ~~~~~~

    ├─ android.media.MediaPlayer instance

    │    Leaking: UNKNOWN

    │    ↓ MediaPlayer.mSubtitleController

    │                  ~~~~~~~~~~~~~~~~~~~

    ├─ android.media.SubtitleController instance

    │    Leaking: UNKNOWN

    │    ↓ SubtitleController.mAnchor

    │                        ~~~~~~~

    ├─ android.widget.VideoView instance

    │    Leaking: YES (View.mContext references a destroyed activity)

    │    View is part of a window view hierarchy

    │    View.mAttachInfo is null (view detached)

    │    View.mID = R.id.video_view

    │    View.mWindowAttachCount = 1

    │    mContext instance of LoginActivity with mDestroyed = true

    │    ↓ View.mContext

    ╰→ LoginActivity instance

        Leaking: YES (ObjectWatcher was watching this because LoginActivity received

        Activity#onDestroy() callback and Activity#mDestroyed is true)

        key = 17268c98-e72a-4afb-9e79-ea4a86bba904

        watchDurationMillis = 5170

        retainedDurationMillis = 166

        mApplication instance of ChatApplication

        mBase instance of androidx.appcompat.view.ContextThemeWrapper

    ====================================

原因分析:

1、引用链结构:MediaPlayer 的SubtitleController 引用了VideoView 中的context对象,而VideoView 对象中的mContext引用到了LoginActivity 中context,导致LoginActivity 无法销毁。

查看源码得知在VideoView 中有如下代码:

final Context context = getContext();

final SubtitleController controller =new SubtitleController(context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);

mMediaPlayer.setSubtitleAnchor(controller, this);

SubtitleController 持有了VideoView 中的context对象,进而MediaPlayer 持有了控件对象,当LoginActivity 无法销毁时却没有释放引用。

泄漏点的root引用是PlayerBase$1.this$0(PlayeBase的子类是MediaPlayer),这是IAppsCallback$Stub的匿名类,在7.0系统可以看到

http://androidxref.com/7.0.0_r1/xref/frameworks/base/media/java/android/media/PlayerBase.java

这个匿名内部类是Binder类,长生命周期持有短生命周期VideoPlayerActivity的引用,导致VideoPlayerActivity的泄漏

再进一步看,发现8.0以上的手机不会出现这个内存泄漏,原来是系统源码已经解决了这个内存泄漏

http://androidxref.com/8.0.0_r4/xref/frameworks/base/media/java/android/media/PlayerBase.java

处理方式是初始化mAppOpsCallback时,new 一个静态内部类,并且这个静态类传入的参数是弱引用

这是8.0开始做的修复

2、解决方法:因为在源码层面无法修改源码,在引用端切断引用链。

import android.content.Context;

import android.os.Build;

import android.util.AttributeSet;

import android.view.ViewGroup;

import android.widget.VideoView;

/**

* 关于Android VideoView导致的内存泄漏的问题

* https://www.jianshu.com/writer#/notebooks/38625762/notes/93298920

*/

public class NoMemoryLeakVideoViewextends VideoView {

public NoMemoryLeakVideoView(Context context) {

super(context.getApplicationContext());

    }

public NoMemoryLeakVideoView(Context context, AttributeSet attrs) {

super(context.getApplicationContext(), attrs);

    }

public NoMemoryLeakVideoView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context.getApplicationContext(), attrs, defStyleAttr);

    }

public void clearMemoryLeak(ViewGroup container) {

suspend();

        clearFocus();

        // VideoView在发生播放错误的时候,会有弹窗错误提示的Dialog,Dialog依赖传入mContext,如果是Application,那么会报崩溃。

        // 解决方法: 通过查看源码发现,在弹这个dialog的时候,会有条件判断,拦截这个条件不弹错误提示的Dialog即不会崩溃,然后这个Dialog在外部回调接口弹出。这个拦截条件是VideoView的setOnErrorListener的实现方法返回true。

        setOnErrorListener((mp, what, extra) ->true);

        setOnPreparedListener(null);

        setOnCompletionListener(null);

        setOnTouchListener(null);

        setOnClickListener(null);

        setOnDragListener(null);

        setOnKeyListener(null);

        setOnLongClickListener(null);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {

setOnApplyWindowInsetsListener(null);

        }

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

setOnScrollChangeListener(null);

        }

setOnFocusChangeListener(null);

        if (container !=null) {

container.removeView(this);

        }

}

}

最后在页面销毁的地方调用clearMemoryLeak方法

相关文章

网友评论

      本文标题:关于Android VideoView导致的内存泄漏的问题

      本文链接:https://www.haomeiwen.com/subject/tcrcnltx.html