Android应用开发(37)LTPO帧率测试基于Surfaceview

Android应用开发学习笔记——目录索引

 参考android官网:

  1. Frame rate  |  Android media  |  Android Developers
  2. 多重刷新率  |  Android 开源项目  |  Android Open Source Project
  3. WindowManager.LayoutParams  |  Android Developers

目前市面上旗舰手机基本都是普及了LTPO屏幕,为了验证LTPO屏幕的DDIC(display driver ID)的硬件刷帧行为(屏幕硬件的刷新可以通过屏幕的AVDD电流信号/屏幕硬件内部的Vsync信号检测),写一个可以指定app出图频率的测试程序(基于Android应用开发(35)SufaceView基本用法 与 Android应用开发(36)帧率API测试基于Surfaceview)。

一、获取屏幕支持的所有帧率

应用程序获取设备实际支持的显示刷新率,可以通过调用 Display.getSupportedModes()获取,Mode.mRefreshRate就是帧率信息,以便安全调用setFrameRate()

// Display.java
Display.Mode[] mSupportedModes = getWindowManager().getDefaultDisplay().getSupportedModes();
for (Display.Mode mode : mSupportedModes) {
   Log.d(TAG, "getSupportedModes: " + mode.toString());
   Log.d(TAG, "getRefreshRate: " + mode.getRefreshRate());
}
 
 
public static final class Mode implements Parcelable {
        public static final Mode[] EMPTY_ARRAY = new Mode[0];
 
        private final int mModeId;
        private final int mWidth;
        private final int mHeight;
        private final float mRefreshRate;
        @NonNull
        private final float[] mAlternativeRefreshRates;
...
}

二、APP设计思路

1. APP 界面设计

  1.  动态显示系统当前使用的帧率和分辨率信息
  2. 系统支持的所有帧率(使用Spinner组件),可下拉选择,通过setFrameRate( )或者PreferredDisplayModeId改变当前系统帧率
  3. 下拉选择APP的绘制频率(使用Spinner组件),提供常用的绘制频率选择项
  4. 输入APP绘制频率(EditTextNumber):如果下拉选择APP的绘制频率无法满足要求,可以输入绘制频率,输入优先。
  5. 确认按键:启动/停止绘制按键
  6. 绘制显示区域:方便直观观察现象

2.设置帧率的方法

同之前一样使用setFrameRate( )/PreferredDisplayModeId/PreferredRefreshRate

setFrameRate()

surface.setFrameRate(contentFrameRate,
    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
    CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS)
;
// 调用setFrameRate()时,最好传入准确的帧速率,而不是四舍五入为整数。例如,在渲染以 29.97Hz 录制的视频时,传入 29.97 而不是四舍五入为 30。
参数:Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE仅适用于视频应用程序。对于非视频用途,请使用FRAME_RATE_COMPATIBILITY_DEFAULT.
选择改变帧速率的策略:
google强烈建议应用在显示电影等长时间运行的视频时调用setFrameRate(fps , FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS) ,其中 fps 是视频的帧速率。
当您预计视频播放持续几分钟或更短时间时,强烈建议您不要调用setFrameRate()带参数CHANGE_FRAME_RATE_ALWAYS。

SurfaceControl.Transaction.setFrameRate()
参数和surface.setFrameRate是一样的

PreferredDisplayModeId:

WindowManager.LayoutParams.preferredDisplayModeId 是应用程序向平台指示其帧速率的另一种方式。

  • 如果应用程序想要更改分辨率或其他显示模式设置,请使用preferredDisplayModeId
  • setFrameRate()如果模式切换是轻量级的并且不太可能被用户注意到,则平台只会响应调用来切换显示模式 。如果应用程序更喜欢切换显示刷新率,即使它需要大量模式切换(例如,在 Android TV 设备上),请使用preferredDisplayModeId.
  • 无法处理以应用程序帧速率倍数运行的显示的应用程序(这需要在每个帧上设置演示时间戳)应使用preferredDisplayModeId.

Display.Mode[] mSupportedModes;
mSupportedModes = getWindowManager().getDefaultDisplay().getSupportedModes();


WindowManager.LayoutParams params = getWindow().getAttributes();
params.preferredDisplayModeId = mSupportedModes[x].getModeId();
getWindow().setAttributes(params);

Log.d(TAG, "RefreshRate:" + mSupportedModes[x].getRefreshRate());

PreferredRefreshRate:

WindowManager.LayoutParams#preferredRefreshRate 在应用程序窗口上设置首选帧速率,并且该速率适用于窗口内的所有表面。无论设备支持的刷新率如何,应用程序都应指定其首选帧速率,类似于 setFrameRate(),以便为调度程序更好地提示应用程序的预期帧速率。

preferredRefreshRate对于使用 的表面将被忽略setFrameRate()。如果可能的话一般使用setFrameRate()

WindowManager.LayoutParams params = getWindow().getAttributes();
params.PreferredRefreshRate = preferredRefreshRate;

getWindow().setAttributes(params);

Log.d(TAG, "preferredRefreshRate:" + preferredRefreshRate);

 3.代码实现

MainActivity.java
public class MainActivity extends AppCompatActivity implements
        View.OnClickListener,
        AdapterView.OnItemSelectedListener,
        SurfaceHolder.Callback,
        DisplayManager.DisplayListener {
    private static final String TAG = "lzl-test-RefreshRateSurfaceViewTest";
    private Vibrator mVibrator;
    private TextView mTextViewInfo;
    private Spinner mSpinnerSystemSupportedRefreshRates, mSpinnerAppDrawFrequencySelect;
    private EditText mEditTextNumber;
    private Button mButton;

    private Display mDisplay;
    private Display.Mode mActiveMode;
    private Display.Mode[] mSupportedModes;
    private ArrayAdapter<Integer> mArrayAdapterSystemSelectableFps;
    private Integer[] mSystemSelectableFps;
    private int mSystemSelectedFps = 0;

    private ArrayAdapter<Integer> mArrayAdapterAppSelectableDrawFrequency;
    private Integer[] mAppSelectableDrawFrequency = {1, 5, 10, 24, 25, 30, 40, 50, 60, 90, 120, 144};
    private int mAppSelectedDrawFrequency = 0;
    private int mAppInputDrawFrequency = 0;
    private int mDoDrawFrequency = 0;

    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private Paint mPaint = new Paint();
    private int mCircleRadius = 10;
    private boolean isRunning = false;
    private boolean isStart = false;
    private long mFrameCount = 0;
    private long mStartTimeMillis = 0, mStopTimeMillis = 0, mDrawCircleTimeMillis = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "onCreate:---------------------------------------------------------start");

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); //设置屏幕不随手机旋转
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); //设置屏幕直向显示
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //设置屏幕全屏
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //设置屏幕不进入休眠

        mDisplay = getWindowManager().getDefaultDisplay();
        mActiveMode = mDisplay.getMode();
        mTextViewInfo = (TextView) findViewById(R.id.textViewInfo);
        mTextViewInfo.setText("系统当前帧率:" + (int) mActiveMode.getRefreshRate() + "Hz, 分辨率: " + mActiveMode.getPhysicalWidth() + " x " + mActiveMode.getPhysicalHeight());

        mSpinnerSystemSupportedRefreshRates = (Spinner) findViewById(R.id.spinnerSystemSupportedRefreshRates);
        mSpinnerSystemSupportedRefreshRates.setOnItemSelectedListener(this);
        mSpinnerAppDrawFrequencySelect = (Spinner) findViewById(R.id.spinnerAppDrawFrequencySelect);
        mSpinnerAppDrawFrequencySelect.setOnItemSelectedListener(this);

        mSupportedModes = getWindowManager().getDefaultDisplay().getSupportedModes();
        ArrayList<Integer> listFps = new ArrayList<>();
        listFps.add((int)mSupportedModes[0].getRefreshRate());
        for (int i = 0; i < mSupportedModes.length; i++) {
            boolean found = false;
            Log.d(TAG, "getSupportedModes: " + mSupportedModes[i].toString());
            for (int index = 0; index < listFps.size(); index++)  {
                if ((int)mSupportedModes[i].getRefreshRate() == listFps.get(index)) {
                    found = true;
                    break;
                }
            }
            if (!found)
                listFps.add((int)mSupportedModes[i].getRefreshRate());
        }
        mSystemSelectableFps = new Integer[listFps.size()];
        for (int i = 0; i < listFps.size(); i++) {
            mSystemSelectableFps[i] = (int)listFps.get(i);
        }
        mArrayAdapterSystemSelectableFps = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, mSystemSelectableFps);
        mArrayAdapterSystemSelectableFps.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // 设置下拉选单的选项样式
        mSpinnerSystemSupportedRefreshRates.setAdapter(mArrayAdapterSystemSelectableFps); // 设置使用 Adapter 对象
        for (int i = 0; i < mSystemSelectableFps.length; i++) {
            if (mSystemSelectableFps[i].intValue() == (int)mActiveMode.getRefreshRate()) {
                mSpinnerSystemSupportedRefreshRates.setSelection(i);
                break;
            }
        }

        mArrayAdapterAppSelectableDrawFrequency = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, mAppSelectableDrawFrequency);
        mArrayAdapterAppSelectableDrawFrequency.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // 设置下拉选单的选项样式
        mSpinnerAppDrawFrequencySelect.setAdapter(mArrayAdapterAppSelectableDrawFrequency); // 设置使用 Adapter 对象
        for (int i = 0; i < mAppSelectableDrawFrequency.length; i++) {
            if (mAppSelectableDrawFrequency[i].intValue() == 60) {
                mSpinnerAppDrawFrequencySelect.setSelection(i);
                break;
            }
        }

        mEditTextNumber = (EditText) findViewById(R.id.editTextNumber);

        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(this);

        mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(this);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            VibratorManager vibratorManager = (VibratorManager)getSystemService(VibratorManager.class);
            mVibrator = vibratorManager.getDefaultVibrator();
        } else {
            mVibrator = (Vibrator)getSystemService(Service.VIBRATOR_SERVICE);
        }

        /* 获取DisplayManager */
        DisplayManager displayManager = (DisplayManager) getSystemService(Service.DISPLAY_SERVICE);
        /* 注册DisplayManager 的DisplayManager.DisplayListener 监听,监听display的变化,如添加/删除display,display帧率变化等 */
        displayManager.registerDisplayListener(this, null);
    }

    @Override
    public void onClick(View view) {
        isStart = !isStart;
        if (isStart) {
            // 获取用户输入的APP刷新率
            if (!mEditTextNumber.getText().toString().isEmpty())
                mAppInputDrawFrequency = Integer.parseInt(mEditTextNumber.getText().toString());
            else
                mAppInputDrawFrequency = 0;

            mDoDrawFrequency = (mAppInputDrawFrequency != 0) ? mAppInputDrawFrequency : mAppSelectedDrawFrequency;

            mEditTextNumber.setEnabled(false);
            mSpinnerSystemSupportedRefreshRates.setEnabled(false);
            mSpinnerAppDrawFrequencySelect.setEnabled(false);

            mButton.setText("停止");
            Log.d(TAG, "按键按下:开始绘制");
            mVibrator.vibrate(80);
            start();
        } else {
            mButton.setText("启动");
            Log.d(TAG, "按键按下:停止绘制");
            stop();
            mVibrator.vibrate(80);

            mEditTextNumber.setEnabled(true);
            mSpinnerSystemSupportedRefreshRates.setEnabled(true);
            mSpinnerAppDrawFrequencySelect.setEnabled(true);
        }
    }

    // 开始绘制
    public void start() {
        isRunning = true;
        mFrameCount = 0;
        mDrawCircleTimeMillis = 0;
        mStartTimeMillis = System.currentTimeMillis();

        new Thread() {
            @Override
            public void run() {
                while (isRunning) {
                    drawCircle();
                    try {
                        long sleep_ms = (1000 / mDoDrawFrequency) - mDrawCircleTimeMillis;
                        Thread.sleep((sleep_ms > 0 )? sleep_ms : 1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
    // 停止绘制
    public void stop() {
        isRunning = false;
        mStopTimeMillis = System.currentTimeMillis();
        drawText();
    }

    // 绘制图形
    private void drawCircle() {
        long now = System.currentTimeMillis();
        if (mSurfaceHolder != null) {
            Canvas canvas = mSurfaceHolder.lockCanvas();
            if (canvas != null) {
                mFrameCount++;
                // 设置画布为灰色背景色
                canvas.drawARGB(255, 13, 61, 80);
                // 画圆
                canvas.drawCircle(canvas.getWidth() / 2, canvas.getWidth() / 2, mCircleRadius, mPaint);
                canvas.drawText("这是第" + mFrameCount + "帧", 20, canvas.getWidth() + 40, mPaint);

                if (mCircleRadius < canvas.getWidth() / 2) {
                    mCircleRadius++;
                } else {
                    mCircleRadius = 10;
                }
                if (canvas != null && mSurfaceHolder != null) {
                    mSurfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
        mDrawCircleTimeMillis = System.currentTimeMillis() - now;
    }

    private void drawText() {
        if (mSurfaceHolder != null) {
            Canvas canvas = mSurfaceHolder.lockCanvas();
            if (canvas != null) {
                long averageTimeNsV1 = 0, averageTimeNsV2 = 0;
                averageTimeNsV1 = 1000000 / mDoDrawFrequency;
                averageTimeNsV2 = (mStopTimeMillis - mStartTimeMillis) * 1000 / mFrameCount;
                canvas.drawText("帧数:" + mFrameCount + " 耗时:" + (mStopTimeMillis - mStartTimeMillis) + "(ms)",
                        20, canvas.getWidth() + 80, mPaint);
                canvas.drawText("理论:" + averageTimeNsV1/1000 + "." + averageTimeNsV1%1000 + "(ms)" +
                                ", 实际:" + averageTimeNsV2/1000 + "." + averageTimeNsV2%1000 + "(ms)",
                        20, canvas.getWidth() + 120, mPaint);
                if (canvas != null && mSurfaceHolder != null) {
                    mSurfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
        Log.d(TAG, "surfaceCreated...");
        if (mSurfaceHolder == null) {
            // 调用getHolder()方法获取SurfaceHolder
            mSurfaceHolder = mSurfaceView.getHolder();
            // 通过 SurfaceHolder.addCallback方法设置:实现SurfaceHolder.Callback回调接口
            mSurfaceHolder.addCallback(this);
        }
        mPaint.setAntiAlias(true); // 设置画笔为无锯齿
        mPaint.setColor(Color.RED); // 设置画笔的颜色
        mPaint.setStrokeWidth(10); // 设置画笔的线宽
        mPaint.setStyle(Paint.Style.FILL); // 设置画笔的类型。STROK表示空心,FILL表示实心
        mPaint.setTextSize(30);
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        Log.d(TAG, "surfaceChanged...");
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
        Log.d(TAG, "surfaceDestroyed...");
        Log.d(TAG, "surfaceDestroyed:停止绘制");
        mSurfaceHolder = null;
        isStart = false;
        mButton.setText("启动");
        stop();
    }

    @Override
    public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
        Log.d(TAG, "Spinner: onItemSelected: position = " + position + ", id = " + id);

        if (adapterView.getId() == R.id.spinnerSystemSupportedRefreshRates) {
            mSystemSelectedFps = mSystemSelectableFps[position].intValue();
            Log.d(TAG, "Spinner: mSystemSelectedFps:" + mSystemSelectedFps);
            int preferredDisplayModeId = 0;
            float preferredRefreshRate = 0f;
            for (Display.Mode mode : mSupportedModes) {
                if ((int)mode.getRefreshRate() == mSystemSelectedFps &&
                        mode.getPhysicalWidth() == mActiveMode.getPhysicalWidth() &&
                        mode.getPhysicalHeight() == mActiveMode.getPhysicalHeight()) {
                    Log.d(TAG, "find mode: " + mode.toString());
                    preferredDisplayModeId = mode.getModeId();
                    preferredRefreshRate = mode.getRefreshRate();
                }
            }
            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                WindowManager.LayoutParams params = getWindow().getAttributes();
                Log.d(TAG, "preferredDisplayModeId: old = " + params.preferredDisplayModeId + ", new = " + preferredDisplayModeId);
                Log.d(TAG, "preferredRefreshRate: old = " + params.preferredRefreshRate + ", new = " + preferredRefreshRate);

                /* WindowManager.LayoutParams#preferredDisplayModeId */
                params.preferredDisplayModeId = preferredDisplayModeId;
                getWindow().setAttributes(params);

                /* WindowManager.LayoutParams#preferredRefreshRate */
                /*
                params.preferredRefreshRate = preferredRefreshRate;
                getWindow().setAttributes(params);
                */

                /* setFrameRate() */
                /*
                if (mSurfaceHolder != null) {
                    mSurfaceHolder.getSurface().setFrameRate(mSystemSelectedFps,
                            Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                            Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
                }
                */
            }

        } else if (adapterView.getId() == R.id.spinnerAppDrawFrequencySelect) {
            mAppSelectedDrawFrequency = mAppSelectableDrawFrequency[position].intValue();
            Log.d(TAG, "Spinner: mAppSelectedDrawFrequency:" + mAppSelectedDrawFrequency);
        } else {
            Log.e(TAG, "Unknown spinner id!");
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> adapterView) {
        Log.d(TAG, "onNothingSelected...");

    }

    @Override
    public void onDisplayAdded(int displayId) {
        Log.d(TAG, "onDisplayAdded...");

    }

    @Override
    public void onDisplayRemoved(int displayId) {
        Log.d(TAG, "onDisplayRemoved...");

    }

    @Override
    public void onDisplayChanged(int displayId) {
        Log.d(TAG, "onDisplayChanged...");

        mActiveMode = mDisplay.getMode();
        mTextViewInfo.setText("系统当前帧率:" + (int) mActiveMode.getRefreshRate() + "Hz, 分辨率: " + mActiveMode.getPhysicalWidth() + " x " + mActiveMode.getPhysicalHeight());

        for (int i = 0; i < mSystemSelectableFps.length; i++) {
            if (mSystemSelectableFps[i].intValue() == (int) mActiveMode.getRefreshRate()) {
                mSpinnerSystemSupportedRefreshRates.setSelection(i);
                break;
            }
        }
    }
}

 layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textViewInfo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="系统当前帧率:"
        android:textColor="#FF0000"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="系统支持的屏幕刷新率:"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewInfo" />
    <Spinner
        android:id="@+id/spinnerSystemSupportedRefreshRates"
        android:layout_width="100dp"
        android:layout_height="32dp"
        android:layout_marginTop="16dp"
        app:layout_constraintEnd_toEndOf="@+id/textView1"
        app:layout_constraintStart_toStartOf="@+id/textView1"
        app:layout_constraintTop_toBottomOf="@+id/textView1" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="选择APP的绘制频率:"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewInfo" />
    <Spinner
        android:id="@+id/spinnerAppDrawFrequencySelect"
        android:layout_width="100dp"
        android:layout_height="32dp"
        android:layout_marginTop="16dp"
        app:layout_constraintEnd_toEndOf="@+id/textView2"
        app:layout_constraintStart_toStartOf="@+id/textView2"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="输入APP绘制频率(Hz):"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="@+id/spinnerSystemSupportedRefreshRates"
        app:layout_constraintStart_toStartOf="@+id/textView1"
        app:layout_constraintTop_toBottomOf="@+id/spinnerSystemSupportedRefreshRates" />
    <EditText
        android:id="@+id/editTextNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:ems="3"
        android:inputType="number"
        app:layout_constraintBottom_toBottomOf="@+id/textView3"
        app:layout_constraintStart_toEndOf="@+id/textView3"
        app:layout_constraintTop_toTopOf="@+id/textView3" />
    
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="启动"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editTextNumber" />

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="300dp"
        android:layout_height="400dp"
        android:layout_marginTop="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

</androidx.constraintlayout.widget.ConstraintLayout>

三、APP运行界面

到【开发者选项】去开启【显示刷新频率】

        


 

四、测试程序完整源码

百度网盘链接:百度网盘 请输入提取码 提取码:test

RefreshRateSurfaceViewTest目录

点此查看Android应用开发学习笔记的完整目录

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liuzl_2010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值