媒体投影
借助 Android 5(API 级别 21)中引入的 android.media.projection API,您可以将设备屏幕中的内容截取为可播放、录制或投屏到其他设备(如电视)的媒体流。
Android 14(API 级别 34)引入了应用屏幕共享功能,让用户可以或许分享单个应用窗口(而非整个设备屏幕),无论窗口模式怎样。应用屏幕共享功能会将状态栏、导航栏、通知和其他系统界面元素从共享表现屏中排除,纵然应用屏幕共享功能用于全屏截取应用也是云云。系统只会分享所选应用的内容。
应用屏幕共享功能可让用户运行多个应用,但仅限于与单个应用共享内容,从而确保用户隐私、提高用户工作服从并增强多使命处置惩罚本领。
权限
如果您的应用以 Android 14 或更高版本为目标平台,则应用清单必须包含 mediaProjection 前台服务类型的权限声明:
- <manifest ...>
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
- <application ...>
- <service
- android:name=".MyMediaProjectionService"
- android:foregroundServiceType="mediaProjection"
- android:exported="false">
- </service>
- </application>
- </manifest>
复制代码 通过调用 startForeground() 启动媒体投影服务。
如果您未在调用中指定前台服务类型,则类型默认为清单中定义的前台服务类型的按位整数。如果清单未指定任何服务类型,系统会抛出 MissingForegroundServiceTypeException。
获取MediaProjection示例(通例实现)
AndroidManifest.xml
- <!-- MediaProjection -->
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
- <application>
- <activity android:name=".MediaProjectionTest"/>
- <service android:name=".MediaProjectionService"
- android:foregroundServiceType="mediaProjection"/>
- </application>
复制代码 Activity
- MediaProjectionManager projMgr;
- final int REQUEST_CODE = 0x101;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- projMgr = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
- startService(new Intent(this, ForgroundMediaProjectionService.class));
- startActivityForResult(projMgr.createScreenCaptureIntent(), REQUEST_CODE);
- }
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if(requestCode == REQUEST_CODE){
- MediaProjection mp = projMgr.getMediaProjection(resultCode, data);
- if(mp != null){
- //mp.stop();
- //获取到MediaProjection后可以通过MediaCodec编码生成图片/视频/H264流...
- }
- }
- }
复制代码 Service
- @Override
- public void onCreate() {
- super.onCreate();
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Notification notification = null;
- Intent activity = new Intent(this, MediaProjectionTest.class);
- activity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel("ScreenRecorder", "Foreground notification",
- NotificationManager.IMPORTANCE_DEFAULT);
- NotificationManager manager = getSystemService(NotificationManager.class);
- manager.createNotificationChannel(channel);
- notification = new Notification.Builder(this, "ScreenRecorder")
- .setContentTitle("Test")
- .setContentText("Test Screencast...")
- .setContentIntent(PendingIntent.getActivity(this, 0x77,
- activity, PendingIntent.FLAG_UPDATE_CURRENT))
- .build();
- }
- startForeground(1, notification);
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
复制代码 启动Acrtivity后会弹出授权提示

点击立即开始 Activity.onActivityResult 可以获取到MediaProjection.
如果App是系统应用(android.uid.systtem), 怎样跳过授权窗?
涉及源码
frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
frameworks/base/media/java/android/media/projection/MediaProjectionManager.java
frameworks/base/core/res/res/values/config.xml
frameworks/base/packages/SystemUI/AndroidManifest.xml
frameworks/base/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
函数createScreenCaptureIntent 返回的Intent 指向的是 SystemUI的一个组件:
frameworks/base/media/java/android/media/projection/MediaProjectionManager.java
- /**
- * Returns an Intent that <b>must</b> be passed to startActivityForResult()
- * in order to start screen capture. The activity will prompt
- * the user whether to allow screen capture. The result of this
- * activity should be passed to getMediaProjection.
- */
- public Intent createScreenCaptureIntent() {
- Intent i = new Intent();
- final ComponentName mediaProjectionPermissionDialogComponent =
- ComponentName.unflattenFromString(mContext.getResources().getString(
- com.android.internal.R.string
- .config_mediaProjectionPermissionDialogComponent));
- i.setComponent(mediaProjectionPermissionDialogComponent);
- return i;
- }
复制代码 frameworks/base/core/res/res/values/config.xml
- <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
复制代码 frameworks/base/packages/SystemUI/AndroidManifest.xml
- <!-- started from MediaProjectionManager -->
- <activity
- android:name=".media.MediaProjectionPermissionActivity"
- android:exported="true"
- android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog"
- android:finishOnCloseSystemDialogs="true"
- android:launchMode="singleTop"
- android:excludeFromRecents="true"
- android:visibleToInstantApps="true"/>
复制代码 MediaProjectionPermissionActivity 就是弹窗的主体
frameworks/base/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mPackageName = getCallingPackage();
- IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
- mService = IMediaProjectionManager.Stub.asInterface(b);
-
- if (mPackageName == null) {
- finish();
- return;
- }
-
- PackageManager packageManager = getPackageManager();
- ApplicationInfo aInfo;
- try {
- aInfo = packageManager.getApplicationInfo(mPackageName, 0);
- mUid = aInfo.uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "unable to look up package name", e);
- finish();
- return;
- }
-
- try {
- if (mService.hasProjectionPermission(mUid, mPackageName)) {
- setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
- finish();
- return;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error checking projection permissions", e);
- finish();
- return;
- }
-
- TextPaint paint = new TextPaint();
- paint.setTextSize(42);
-
- CharSequence dialogText = null;
- CharSequence dialogTitle = null;
- if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
- dialogText = getString(R.string.media_projection_dialog_service_text);
- dialogTitle = getString(R.string.media_projection_dialog_service_title);
- } else {
- String label = aInfo.loadLabel(packageManager).toString();
-
- // If the label contains new line characters it may push the security
- // message below the fold of the dialog. Labels shouldn't have new line
- // characters anyways, so just truncate the message the first time one
- // is seen.
- final int labelLength = label.length();
- int offset = 0;
- while (offset < labelLength) {
- final int codePoint = label.codePointAt(offset);
- final int type = Character.getType(codePoint);
- if (type == Character.LINE_SEPARATOR
- || type == Character.CONTROL
- || type == Character.PARAGRAPH_SEPARATOR) {
- label = label.substring(0, offset) + ELLIPSIS;
- break;
- }
- offset += Character.charCount(codePoint);
- }
-
- if (label.isEmpty()) {
- label = mPackageName;
- }
-
- String unsanitizedAppName = TextUtils.ellipsize(label,
- paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
- String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
-
- String actionText = getString(R.string.media_projection_dialog_text, appName);
- SpannableString message = new SpannableString(actionText);
-
- int appNameIndex = actionText.indexOf(appName);
- if (appNameIndex >= 0) {
- message.setSpan(new StyleSpan(Typeface.BOLD),
- appNameIndex, appNameIndex + appName.length(), 0);
- }
- dialogText = message;
- dialogTitle = getString(R.string.media_projection_dialog_title, appName);
- }
-
- View dialogTitleView = View.inflate(this, R.layout.media_projection_dialog_title, null);
- TextView titleText = (TextView) dialogTitleView.findViewById(R.id.dialog_title);
- titleText.setText(dialogTitle);
-
- mDialog = new AlertDialog.Builder(this)
- .setCustomTitle(dialogTitleView)
- .setMessage(dialogText)
- .setPositiveButton(R.string.media_projection_action_text, this)
- .setNegativeButton(android.R.string.cancel, this)
- .setOnCancelListener(this)
- .create();
-
- mDialog.create();
- mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
-
- final Window w = mDialog.getWindow();
- w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
- w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-
- mDialog.show();
- }
-
- private Intent getMediaProjectionIntent(int uid, String packageName)
- throws RemoteException {
- IMediaProjection projection = mService.createProjection(uid, packageName,
- MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
- Intent intent = new Intent();
- intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
- return intent;
- }
复制代码
- 申请成功后返回结果给到申请的Activity:
在getMediaProjectionIntent函数中, 创建了IMediaProjection并通过Intent返回给了调用的App
- setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
复制代码- IMediaProjection projection = mService.createProjection(uid, packageName,
- MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
- Intent intent = new Intent();
- intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
- getMediaProjection(resultCode, data)
复制代码 - Activity 调用 getMediaProjection 获取MediaProjection
frameworks/base/media/java/android/media/projection/MediaProjectionManager.java
- public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
- if (resultCode != Activity.RESULT_OK || resultData == null) {
- return null;
- }
- IBinder projection = resultData.getIBinderExtra(EXTRA_MEDIA_PROJECTION);
- if (projection == null) {
- return null;
- }
- return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
- }
复制代码 总的来说, 这个流程稍微绕了一点路:
createProjection的实现
frameworks/base/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
- @Override // Binder call
- public IMediaProjection createProjection(int uid, String packageName, int type,
- boolean isPermanentGrant) {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
- + "projection permission");
- }
- if (packageName == null || packageName.isEmpty()) {
- throw new IllegalArgumentException("package name must not be empty");
- }
-
- final UserHandle callingUser = Binder.getCallingUserHandle();
- long callingToken = Binder.clearCallingIdentity();
-
- MediaProjection projection;
- try {
- ApplicationInfo ai;
- try {
- ai = mPackageManager.getApplicationInfoAsUser(packageName, 0, callingUser);
- } catch (NameNotFoundException e) {
- throw new IllegalArgumentException("No package matching :" + packageName);
- }
-
- projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
- ai.isPrivilegedApp());
- if (isPermanentGrant) {
- mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
- projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
- }
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
- return projection;
- }
-
复制代码 通过反射, 调用MediaProjectionService的createProjection
注意: 此方法必要有系统权限(android.uid.system)
- //android.os.ServiceManager;
- static Object getService(String name){
- try {
- Class ServiceManager = Class.forName("android.os.ServiceManager");
- Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
- return getService.invoke(null, name);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- return null;
- }
- @SuppressLint("SoonBlockedPrivateApi")
- static Object asInterface(Object binder){
- try {
- Class IMediaProjectionManager_Stub = Class.forName("android.media.projection.IMediaProjectionManager$Stub");
- Method asInterface = IMediaProjectionManager_Stub.getDeclaredMethod("asInterface", IBinder.class);
- return asInterface.invoke(null, binder);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- return null;
- }
- // private IMediaProjectionManager mService;
- //android.media.projection.IMediaProjectionManager
- @SuppressLint("SoonBlockedPrivateApi")
- public static MediaProjection createProjection(){
- //Context.java public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
- //IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
- // mService = IMediaProjectionManager.Stub.asInterface(b);
- IBinder b = (IBinder) getService("media_projection");
- Object mService = asInterface(b) ;
- //IMediaProjection projection = mService.createProjection(uid, packageName,
- //MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
- //public static final int TYPE_SCREEN_CAPTURE = 0;
- try {
- Logger.i("createProjection", "createProjection");
- Class IMediaProjectionManager = Class.forName("android.media.projection.IMediaProjectionManager");
- // public IMediaProjection createProjection(int uid, String packageName, int type, boolean isPermanentGrant)
- Method createProjection = IMediaProjectionManager.getDeclaredMethod("createProjection", Integer.TYPE, String.class, Integer.TYPE, Boolean.TYPE);
- Object projection = createProjection.invoke(mService, android.os.Process.myUid(), App.getApp().getPackageName(),
- 0, false);
- Logger.i("createProjection", "projection created!");
- //android.media.projection.IMediaProjection;
- Class IMediaProjection = IInterface.class;//Class.forName("android.media.projection.IMediaProjection");
- Method asBinder = IMediaProjection.getDeclaredMethod("asBinder");
- Logger.i("createProjection", "asBinder found");
- Intent intent = new Intent();
- // public static final String EXTRA_MEDIA_PROJECTION =
- // "android.media.projection.extra.EXTRA_MEDIA_PROJECTION";
- //Bundle extra = new Bundle();
- //extra.putBinder("android.media.projection.extra.EXTRA_MEDIA_PROJECTION", (IBinder)asBinder.invoke(projection));
- //intent.putExtra("android.media.projection.extra.EXTRA_MEDIA_PROJECTION", (IBinder)asBinder.invoke(projection));
- intent.putExtra(Intent.EXTRA_RETURN_RESULT, Activity.RESULT_OK);
- Object projBinder = asBinder.invoke(projection);
- Logger.i("createProjection", "asBinder invoke success.");
- //intent.getExtras().putBinder("android.media.projection.extra.EXTRA_MEDIA_PROJECTION", (IBinder)projBinder);
- Method putExtra = Intent.class.getDeclaredMethod("putExtra", String.class, IBinder.class);
- putExtra.invoke(intent, "android.media.projection.extra.EXTRA_MEDIA_PROJECTION", (IBinder)projBinder);
- Logger.i("createProjection", "putExtra with IBinder success.");
- MediaProjectionManager projMgr = App.getApp().getMediaProjectionManager();
- MediaProjection mp = projMgr.getMediaProjection(Activity.RESULT_OK, intent);
- Logger.i("createProjection", "getMediaProjection " + (mp == null ? " Failed" : "Success"));
- //new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
- //if(mp != null)mp.stop();
- return mp;
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- return null;
- }
复制代码 参考
Android截屏录屏MediaProjection分享
Android录屏的三种方案
媒体投影
[Android] 使用MediaProjection截屏
android设备间实现无线投屏
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |