Android屏幕录制

这里使用Java语言编写实现,完整代码如下:

文件 AndroidMainfest.xml 的主要配置

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.demoapp">

    <!-- for 屏幕录制 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true">
        <activity
                android:name=".MainActivity"
                android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>


        <activity
                android:name=".ScreenRecordingActivity"
                android:exported="false"
                android:launchMode="singleTask"></activity>

        <service
                android:name=".ScreenRecordingService"
                android:enabled="true"
                android:exported="true"
                android:foregroundServiceType="mediaProjection" />
    </application>

</manifest>

文件activity_screen_recording.xml的内容

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"
        android:orientation="vertical"
        tools:context=".MainActivity"
        tools:ignore="MissingConstraints">

    <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:gravity="center_horizontal"
            android:orientation="vertical"
            android:paddingTop="0dp">

        <!-- 注: SurfaceView不能设置background属性 -->
        <!-- <SurfaceView
                android:id="@+id/surface_view"
                android:layout_width="fill_parent"
                android:layout_height="80dp" /> -->

        <TextView
                android:id="@+id/textview_recording_info"
                android:layout_width="wrap_content"
                android:layout_height="100dp"
                android:layout_marginTop="20dp"
                android:layout_gravity="center"
                android:textColor="#333333"
                android:textSize="20sp"
                android:textStyle="bold"
                android:text="--"
                android:maxLines="5" />

        <Button
                android:id="@+id/btn_recording_start"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="40dp"
                android:text="开始录制" />

        <Button
                android:id="@+id/btn_recording_stop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="40dp"
                android:text="停止录制" />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

文件ScreenRecordingActivity.java的完整代码

java 复制代码
package com.example.demoapp;

import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.example.myandroidnfcapp.R;

/**
 * @Function: 屏幕录制视频。
 * @Author: ChengJh。
 * @Date: 2023/09/06。
 * @Description: https://blog.csdn.net/qq_46546793/article/details/123279152 和 https://blog.csdn.net/weixin_42602900/article/details/128340037 。
 */
public class ScreenRecordingActivity extends AppCompatActivity {
  private static final String TAG = ScreenRecordingActivity.class.getSimpleName();

  //录屏服务
  private ScreenRecordingService mService;
  private int mServiceStatus = ScreenRecordingService.statusInit;
  private boolean clickedStart = false;
  private ActivityResultLauncher activityLauncher;

  private TextView textViewInfo;

  private boolean prepared = false;

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

    ActionBar actionBar = getSupportActionBar();
    if (actionBar != null) {
      actionBar.setHomeButtonEnabled(true);
      actionBar.setDisplayHomeAsUpEnabled(true);
      actionBar.setTitle("录屏管理页面");
    }

    textViewInfo = findViewById(R.id.textview_recording_info);

    Button btnStart = (Button) findViewById(R.id.btn_recording_start);
    btnStart.setOnClickListener(view -> {
      startRecord();
    });

    Button btnStop = (Button) findViewById(R.id.btn_recording_stop);
    btnStop.setOnClickListener(view -> {
      stopRecord();
    });

    //注: registerForActivityResult()方法, 只能在onCreate()中注册, onStart()之后就不能注册了。。
    activityLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(), o -> {
          System.out.println(TAG + "_registerForActivityResult_callback_" + o.getResultCode());
          if (o.getResultCode() == Activity.RESULT_OK) {
            actionAfterConfirmAgreed(o.getResultCode(), o.getData());
          }
        });
  }

  @Override
  public void onStart() {
    super.onStart();
  }

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

    /** 在Activity的六个核心回调 onCreate()、onStart()、onResume()、onPause()、onStop() 和 onDestroy() 中,
     *  onStart()、onResume()、onPause()、onStop() 会在 onRestart() 之后再次触发。
     *  因此, 有的任务如果放在其中某个函数内触发, 可能需要加个标记变量控制一下。
     */
    if (!prepared) {
      prepared = true;
      checkPermission();
    }
  }

  @Override
  public void onDestroy() {
    /** 页面销毁的时候, 可以不停止录屏 */
    // stopRecord();
    if (mServiceStatus >= ScreenRecordingService.statusServiceConnected) {
      unbindService(serviceConnection);
    }
    super.onDestroy();
  }

  // 标题栏返回按钮事件。
  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case android.R.id.home:
        //this.finish();
        this.onBackPressed();
        return true;
    }
    return super.onOptionsItemSelected(item);
  }

  // @Override
  // //返回方法, 获取返回的信息。
  // protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  //   super.onActivityResult(requestCode, resultCode, data);
  //   System.out.println(TAG + "_onActivityResult");
  //   //首先判断请求码是否一致, 结果是否ok 。
  //   if (requestCode == ScreenRecordingService.requestCode && resultCode == RESULT_OK) {
  //     actionAfterUserAgree(resultCode, data);
  //   }
  // }

  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
      grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    System.out.println(TAG + "_onRequestPermissionsResult");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && requestCode == ScreenRecordingService.requestCodeForPermisssion) {
      for (int i = 0; i < permissions.length; i++) {
        if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
          return;
        }
      }
      connectService();
    }
  }

  /**
   * 权限申请
   */
  private void checkPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      String[] permissions = new String[]{
          Manifest.permission.RECORD_AUDIO,
          Manifest.permission.WRITE_EXTERNAL_STORAGE,
          Manifest.permission.READ_EXTERNAL_STORAGE
      };
      for (String permission : permissions) {
        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
          ActivityCompat.requestPermissions(this, permissions, ScreenRecordingService.requestCodeForPermisssion);
          return;
        }
      }
    }

    connectService();
  }

  //连接服务。
  public void connectService() {
    System.out.println(TAG + "_connectService");
    //通过intent为中介绑定Service, 会自动创建。
    Intent intent = new Intent(this, ScreenRecordingService.class);
    //绑定过程连接, 选择绑定模式。
    bindService(intent, serviceConnection, BIND_AUTO_CREATE);
  }

  //连接服务成功与否, 具体连接过程。
  //调用连接接口, 实现连接, 回调连接结果。
  private final ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
      System.out.println(TAG + "_ServiceConnection_onServiceConnected");
      //服务连接成功, 需要通过Binder获取服务, 达到Activity和Service通信的目的
      ScreenRecordingService.ScreenRecordBinder binder = (ScreenRecordingService.ScreenRecordBinder) iBinder;
      //通过Binder获取Service
      mService = binder.getScreenRecordService();
      mService.setListener(new ScreenRecordingService.ScreenRecordingServiceListener() {
        @Override
        public void onPrepareComplete() {
          if (clickedStart && mService.startRecord()) {
            textViewInfo.setText("正在录屏");
          }
        }

        @Override
        public void onRecordingFinish(String videoPath) {
          textViewInfo.setText("视频文件路径: " + videoPath);
        }
      });
      if (mService.isRunning()) {
        System.out.println(TAG + "_ServiceConnection_RunAlready");
        mServiceStatus = ScreenRecordingService.statusConfirmAgreed;
        textViewInfo.setText("正在录屏");
        if (clickedStart) {
          Toast.makeText(ScreenRecordingActivity.this, "已在录屏服务中", Toast.LENGTH_SHORT).show();
        }
        return;
      }
      mServiceStatus = ScreenRecordingService.statusServiceConnected;
      if (clickedStart) {
        actionAfterServiceConnected();
      }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
      //连接失败。
      Toast.makeText(ScreenRecordingActivity.this, "录屏服务未连接成功", Toast.LENGTH_SHORT).show();
    }
  };

  private void actionAfterConfirmAgreed(int resultCode, @Nullable Intent data) {
    mServiceStatus = ScreenRecordingService.statusConfirmAgreed;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      if (mService != null) {
        //获取录屏屏幕范围参数。
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        mService.setConfig(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);

        Intent intent = new Intent(this, ScreenRecordingService.class);
        intent.putExtra("code", resultCode);
        intent.putExtra("data", data);
        startForegroundService(intent);
      } else {
        System.out.println(TAG + "_onActivityResult_exception");
      }
    }
  }

  private void actionAfterServiceConnected() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      //获取到服务, 初始化录屏管理者。
      MediaProjectionManager projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
      //通过管理者, 创建录屏请求, 通过Intent。
      Intent captureIntent = projectionManager.createScreenCaptureIntent();
      //将请求码作为标识一起发送, 调用该接口, 需有返回方法。
      //startActivityForResult(captureIntent, ScreenRecordingService.requestCode);
      activityLauncher.launch(captureIntent);
    }
  }

  private void nextActionByStatus() {
    switch (mServiceStatus) {
      case ScreenRecordingService.statusInit:
        //点击请求录屏时, 第一件事, 检查权限。
        checkPermission();
        break;
      case ScreenRecordingService.statusPermissionOK:
        connectService();
        break;
      case ScreenRecordingService.statusServiceConnected:
        actionAfterServiceConnected();
        break;
      default:
        if (mService != null) {
          if (mService.startRecord()) {
            textViewInfo.setText("正在录屏");
          }
        }
        break;
    }
  }

  private void startRecord() {
    System.out.println(TAG + "_startRecord_" + (null == mService));
    clickedStart = true;
    if (mService != null && mService.isRunning()) {
      //如果在录制, 弹出提示。
      Toast.makeText(ScreenRecordingActivity.this, "当前正在录屏, 请不要重复点击哦", Toast.LENGTH_SHORT).show();
    } else {
      //如果不在录制, 就开启录制。
      nextActionByStatus();
    }
  }

  private void stopRecord() {
    clickedStart = false;
    if (null == mService || !mService.isRunning()) {
      //没有录屏, 无需停止, 弹出提示。
      Toast.makeText(ScreenRecordingActivity.this, "还没有录屏, 无需停止", Toast.LENGTH_SHORT).show();
    } else {
      //停止录屏。
      mService.stopRecord();
    }
  }

}

文件 ScreenRecordingService.java 的完整代码

java 复制代码
package com.example.demoapp;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.Nullable;

import com.example.myandroidnfcapp.MainActivity;
import com.example.myandroidnfcapp.R;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

public class ScreenRecordingService extends Service {
  private static final String TAG = ScreenRecordingService.class.getSimpleName();

  public static final int statusInit = 0;
  public static final int statusPermissionOK = 1;
  public static final int statusServiceConnected = 2;
  public static final int statusConfirmAgreed = 3;

  public static final int requestCodeForPermisssion = 110;
  public static final int requestCode = 111;

  private MediaProjectionManager mediaProjectionManager;
  //录屏工具MediaProjection。
  private MediaProjection mediaProjection;
  //录像机MediaRecorder。
  private MediaRecorder mediaRecorder;
  //用于录屏的虚拟屏幕。
  private VirtualDisplay virtualDisplay;
  //录制屏幕的宽高像素。
  private int mWidth = 720;
  private int mHeight = 1280;
  private int mDpi = 1;
  //视频存储路径
  private String mVideoPath = "";
  //标志, 判断是否正在录屏
  private boolean running = false;

  //回调接口, 以及接口中要做的事。
  public interface ScreenRecordingServiceListener {
    void onPrepareComplete();

    void onRecordingFinish(String videoPath);
  }

  private ScreenRecordingServiceListener listener;

  @Override
  public void onCreate() {
    super.onCreate();
    // HandlerThread serviceThread = new HandlerThread("service_thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);
    // serviceThread.start();
    // running = false;
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    // 注: mediaProjection的生成 必须在Service中进行。
    createNotificationChannel();

    int resultCode = intent.getIntExtra("code", -1);
    Intent resultData = intent.getParcelableExtra("data");
    Log.i(TAG, "onStartCommand_resultCode=" + resultCode);
    Log.i(TAG, "onStartCommand_resultData=" + resultData);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      if (null == mediaProjectionManager) {
        mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
      }
      mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, Objects.requireNonNull(resultData));
      //mediaProjection = ((MediaProjectionManager) Objects.requireNonNull(getSystemService(Context.MEDIA_PROJECTION_SERVICE))).getMediaProjection(resultCode, resultData);
      Log.i(TAG, "mediaProjection_created");
    }

    if (listener != null) {
      listener.onPrepareComplete();
    }

    return super.onStartCommand(intent, flags, startId);
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
  }

  @Override
  public boolean onUnbind(Intent intent) {
    return super.onUnbind(intent);
  }

  //返回的Binder。
  public class ScreenRecordBinder extends Binder {
    //返回Service的方法。
    public ScreenRecordingService getScreenRecordService() {
      return ScreenRecordingService.this;
    }
  }

  @Nullable
  @Override
  //返回一个Binder用于通信, 需要一个获取Service的方法。
  public IBinder onBind(Intent intent) {
    return new ScreenRecordBinder();
  }

  //设置需要录制的屏幕参数。
  public void setConfig(int width, int height, int dpi) {
    mWidth = width;
    mHeight = height;
    mDpi = dpi;
  }

  public void setMediaProjectionManager(MediaProjectionManager projectionManager) {
    mediaProjectionManager = projectionManager;
  }

  public void setListener(ScreenRecordingServiceListener callback) {
    listener = callback;
  }

  //返回判断, 判断其是否在录屏。
  public boolean isRunning() {
    return running;
  }

  //服务的两个主要逻辑之一 ~ 开始录屏。
  public boolean startRecord() {
    Log.i(TAG, "startRecord");
    //首先判断是否有录屏工具以及是否在录屏
    if (null == mediaProjection || running) {
      return false;
    }
    //初始化录像机, 录音机Recorder。
    createRecorder();
    //根据获取的屏幕参数创建虚拟的录屏屏幕。
    createVirtualDisplay();
    //本来不加异常也可以, 但是这样就不知道是否start成功。
    //万一start没有成功, 但是running置为true了, 就产生了错误也无提示。
    //提示开始录屏了, 但是并没有工作。
    try {
      //准备工作都完成了, 可以开始录屏了。
      mediaRecorder.start();
      //标志位改为正在录屏。
      running = true;
      Toast.makeText(this, "录屏开启成功", Toast.LENGTH_SHORT).show();
      return true;
    } catch (Exception e) {
      e.printStackTrace();
      //有异常, start出错, 没有开始录屏。
      Toast.makeText(this, "录屏开启失败", Toast.LENGTH_SHORT).show();
      //标志位变回没有录屏的状态。
      running = false;
      return false;
    }
  }

  //服务的两个主要逻辑之一 ~ 停止录屏。
  public boolean stopRecord() {
    Log.i(TAG, "stopRecord");
    if (!running) {
      //没有在录屏, 无法停止。
      return false;
    }
    //无论设备是否还原或者有异常, 但是确实录屏结束, 修改标志位为未录屏。
    running = false;
    //本来加不加捕获异常都可以, 但是为了用户体验度, 添加会更好。
    try {
      //Recorder停止录像, 重置还原, 以便下一次使用。
      mediaRecorder.stop();
      mediaRecorder.reset();
      //释放virtualDisplay的资源。
      virtualDisplay.release();
    } catch (Exception e) {
      e.printStackTrace();
      //有异常, 保存失败。
      Toast.makeText(this, "录屏结束异常", Toast.LENGTH_SHORT).show();
      return false;
    }
    //无异常, 保存成功。
    Toast.makeText(this, "录屏结束 而且 保存成功", Toast.LENGTH_SHORT).show();
    if (listener != null) {
      listener.onRecordingFinish(mVideoPath);
    }
    return true;
  }

  //初始化Recorder录像机。
  public void createRecorder() {
    Log.i(TAG, "createRecorder");
    //创建Recorder。
    mediaRecorder = new MediaRecorder();
    //设置音频来源。
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    //设置视频来源。
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
    //设置视频格式为mp4。
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    //设置视频存储地址。
    mVideoPath = getOutputFile().getAbsolutePath();
    //保存在该位置。
    mediaRecorder.setOutputFile(mVideoPath);
    //设置视频大小, 清晰度。
    mediaRecorder.setVideoSize(mWidth, mHeight);
    //设置视频编码为H264。
    mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    //设置音频编码。
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    //设置视频码率。
    mediaRecorder.setVideoEncodingBitRate(2 * mWidth * mHeight);
    mediaRecorder.setVideoFrameRate(16);
    //初始化完成, 进入准备阶段, 准备被使用。
    try {
      mediaRecorder.prepare();
    } catch (IOException e) {
      e.printStackTrace();
      //异常提示
      Toast.makeText(this, "Recorder录像机准备失败", Toast.LENGTH_SHORT).show();
    }
  }

  public void createVirtualDisplay() {
    //虚拟屏幕通过MediaProjection获取, 传入一系列传过来的参数。
    try {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        virtualDisplay = mediaProjection.createVirtualDisplay("VirtualScreen", mWidth, mHeight, mDpi,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mediaRecorder.getSurface(), null, null);
      }
    } catch (Exception e) {
      e.printStackTrace();
      Toast.makeText(this, "virtualDisplay创建异常", Toast.LENGTH_SHORT).show();
    }
  }

  //获取输出存储文件夹的位置。
  public static File getOutputDirectory() {
    String directoryFilePathName = Environment.
        getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        .getAbsolutePath()
        + "/ScreenRecording/";
    //创建该文件夹。
    File directoryFile = new File(directoryFilePathName);
    if (!directoryFile.exists()) {
      //如果该文件夹不存在。
      if (!directoryFile.mkdirs()) {
        //如果没有创建成功。
        return null;
      }
    }
    //创建成功了, 返回该目录。
    return directoryFile;
  }

  private File getOutputFile() {
    File directoryFile = getOutputDirectory();
    File file = new File(directoryFile, "SR" + System.currentTimeMillis() + ".mp4");
    // if (!file.exists()) {
    //   try {
    //     file.createNewFile();
    //   } catch (Exception e) {
    //     e.printStackTrace();
    //   }
    // }
    Log.i(TAG, "filePath_" + file.getAbsolutePath());
    return file;
  }

  private void createNotificationChannel() {
    Notification.Builder builder = new Notification.Builder(this.getApplicationContext()); //获取一个Notification构造器。
    Intent nIntent = new Intent(this, MainActivity.class); //点击后跳转的界面, 可以设置跳转数据。

    builder.setContentIntent(PendingIntent.getActivity(this, 0, nIntent, 0))
        .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 设置下拉列表中的图标(大图标)
        //.setContentTitle("ScreenRecording") // 设置下拉列表里的标题
        .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
        .setContentText("is running......") // 设置上下文内容
        .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间

    /** 以下是对Android 8.0的适配 */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      final String channelId = "MyChannelId";
      final String channelName = "MyChannelName";

      //普通notification适配。
      builder.setChannelId(channelId);

      //前台服务notification适配。
      NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
      NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
      notificationManager.createNotificationChannel(channel);
    }

    Notification notification = builder.build(); // 获取构建好的Notification对象。
    notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音。
    startForeground(requestCode, notification);
  }

}

特别鸣谢下面链接:

(0) https://blog.csdn.net/qq_46546793/article/details/123279152

(0) https://blog.csdn.net/weixin_42602900/article/details/128340037

相关推荐
艾迪的技术之路6 分钟前
redisson使用lock导致死锁问题
java·后端·面试
mmoyula7 分钟前
【RK3568 驱动开发:实现一个最基础的网络设备】
android·linux·驱动开发
今天背单词了吗98024 分钟前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
天天摸鱼的java工程师27 分钟前
使用 Spring Boot 整合高德地图实现路线规划功能
java·后端
东阳马生架构42 分钟前
订单初版—2.生单链路中的技术问题说明文档
java
sam.li1 小时前
WebView安全实现(一)
android·安全·webview
咖啡啡不加糖1 小时前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南1 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
DKPT1 小时前
Java享元模式实现方式与应用场景分析
java·笔记·学习·设计模式·享元模式