【Android 性能优化:内存篇】——ExoPlayer 释放后内存没有恢复问题探索

背景

最近笔者承接项目的内存优化指标,在内存调研的过程中发现项目中视频播放结束后,内存没有恢复到播放前到水平。项目中用的 EXO 版本为2.19.1,并且笔者自己也写了个简单的 Demo,发现也是如此。虽然有一些偏门方法可以优化,但是暂时还是未能正面突破,各位看官,如果有什么idea,欢迎留言多多指教~

分析

笔者的 Demo 如下

java 复制代码
 api 'com.google.android.exoplayer:exoplayer:2.19.1'

VideoTestFragment.java

java 复制代码
package com.mikel.projectdemo.uiframework;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ui.PlayerView;
import com.mikel.projectdemo.R;
import org.jetbrains.annotations.NotNull;



public class VideoTestFragment extends Fragment {
    public static VideoTestFragment build() {
        return new VideoTestFragment();
    }

    private Context mContext;
    private SimpleExoPlayer mSimpleExoPlayer;
    private PlayerView playerView;

    @Override
    public View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable @org.jetbrains.annotations.Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        mContext = getActivity();
        View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_video_item, null, true);
        initUI(rootView);
        return rootView;
    }

    private void initUI(View rootView) {
        mSimpleExoPlayer = new SimpleExoPlayer.Builder(getActivity()).build();
        // 准备要播放的媒体资源
        MediaItem mediaItem = MediaItem.fromUri("https://vfx.mtime.cn/Video/2019/01/15/mp4/190115161611510728_480.mp4");
        mSimpleExoPlayer.setMediaItem(mediaItem);
        // 将ExoPlayer关联到要显示视频的View
        playerView = rootView.findViewById(R.id.player_view);
        playerView.setPlayer(mSimpleExoPlayer);
    }

    public void startPlay() {
        // 准备播放器
        mSimpleExoPlayer.prepare();
        mSimpleExoPlayer.play();
    }

    /**
     * 停止播放
     */
    public void stopPlay() {
        pausePlay();
        if(mSimpleExoPlayer != null) {
            mSimpleExoPlayer.release();
            mSimpleExoPlayer = null;
        }
    }

    public void resumePlay() {
        if(mSimpleExoPlayer != null) {
            mSimpleExoPlayer.setPlayWhenReady(true);
        } else {
            startPlay();
        }
    }

    public void pausePlay() {
        if(mSimpleExoPlayer != null) {
            mSimpleExoPlayer.setPlayWhenReady(false);
        }
    }


    @Override
    public void onDestroyView() {
        super.onDestroyView();
        stopPlay();
    }

    @Override
    public void onResume() {
        super.onResume();
        resumePlay();
    }

    @Override
    public void onStop() {
        super.onStop();
        pausePlay();
    }
}

fragment_video_item.xml

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/player_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </com.google.android.exoplayer2.ui.PlayerView>
</FrameLayout>

VideoTestActivity.java

java 复制代码
public class VideoTestActivity extends AppCompatActivity {

    public static void startActivity(Context context) {
        Intent intent = new Intent(context, VideoTestActivity.class);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_test);
        FragmentManager fragmentManager = getSupportFragmentManager();
        VideoTestFragment videoTestFragment = VideoTestFragment.build();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.fragment_container, videoTestFragment);
        fragmentTransaction.commit();

    }
}

activity_video_test.xml

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </FrameLayout>
</FrameLayout>

打开播放页面前和播放后关闭页面,内存水位如下:

内存水位简直毫无波澜,笔者也在 ExoPlayer 上发现不少相关 Issue:

https://github.com/google/ExoPlayer/issues/9755

Memory leak · Issue #1855 · google/ExoPlayer · GitHub

issue 里有一个方法是说在页面onDestroy的时候不仅释放Exoplayer, 还需要加上 simpleExoPlayerView.setPlayer(null),并且把 simpleExoPlayerView也设置为空 ,笔者尝试了下,内存水位依旧没有太大变化

解决方案探索

方案1 独立进程

业务允许的情况下,把播放页面设置成独立进程,

java 复制代码
        <activity android:name=".video.VideoTestActivity"
            android:process=":video">

退出页面后调用

复制代码
android.os.Process.killProcess(android.os.Process.myPid());

该方案适合播放场景单一,使用Activity 来承接视频播放,播放结束后少频繁进入播放页面

方案 2 主动触发 gc

如果业务限制,无法把播放页面放到独立进程,尝试下 VideoFragment onDestroy 的时候主动 Runtime.getRuntime().gc()

该方案剑走偏峰,也是适合播放场景单一,不是频繁打开播放页面的场景,否则频繁手动 gc 可能带来卡顿的性能问题。

各位看官,如果对 ExoPlayer 研究深入或者有什么idea,欢迎留言多多指教~

相关推荐
逻极5 小时前
HarmonyOS 5 鸿蒙应用性能优化与调试技巧
华为·性能优化·harmonyos·鸿蒙
代码s贝多芬的音符6 小时前
ios android 小程序 蓝牙 CRC16_MODBUS
android·ios·小程序
言德斐6 小时前
SQL性能优化的思路及策略
数据库·sql·性能优化
2501_915918418 小时前
iOS 混淆实战 多工具组合完成 IPA 混淆、加固与工程化落地(iOS混淆|IPA加固|无源码混淆|Ipa Guard|Swift Shield)
android·ios·小程序·https·uni-app·iphone·webview
雨白8 小时前
让协程更健壮:全面的异常处理策略
android·kotlin
Jeled9 小时前
AI: 生成Android自我学习路线规划与实战
android·学习·面试·kotlin
游戏开发爱好者810 小时前
如何系统化掌握 iOS 26 App 耗电管理,多工具协作
android·macos·ios·小程序·uni-app·cocoa·iphone
shaominjin12310 小时前
android在sd卡中可以mkdir, 但是不可以createNewFile
android·开发语言·python
AI科技星11 小时前
垂直原理:宇宙的沉默法则与万物运动的终极源头
android·服务器·数据结构·数据库·人工智能
用户416596736935512 小时前
Kotlin Coroutine Flow 深度解析:剖析 `flowOn` 与上下文切换的奥秘
android