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

相关推荐
大白要努力!几秒前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
色空大师3 分钟前
23种设计模式
java·开发语言·设计模式
闲人一枚(学习中)3 分钟前
设计模式-创建型-建造者模式
java·设计模式·建造者模式
2202_7544215421 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
蓝染-惣右介23 分钟前
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
java·数据库·tomcat·mybatis
小林想被监督学习24 分钟前
idea怎么打开两个窗口,运行两个项目
java·ide·intellij-idea
HoneyMoose26 分钟前
IDEA 2024.3 版本更新主要功能介绍
java·ide·intellij-idea
我只会发热28 分钟前
Java SE 与 Java EE:基础与进阶的探索之旅
java·开发语言·java-ee
是老余29 分钟前
本地可运行,jar包运行错误【解决实例】:通过IDEA的maven package打包多模块项目
java·maven·intellij-idea·jar
crazy_wsp29 分钟前
IDEA怎么定位java类所用maven依赖版本及引用位置
java·maven·intellij-idea