MediaProjection和MediaCodec分析

3,225 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

前段时间在Andorid平台实现了屏幕直播,现将其整理一下,用到的知识点主要为:MediaProjection和MediaCodec。

一.MediaProjection获取

      MediaProjection是Android5.0后提出的一套用于录制屏幕的API,无需root权限。与 MediaProjection协同的类有 MediaProjectionManager, MediaCodec。使用MediaProjection需要在AndroidManifest.xml中加入以下权限:

<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
a.获取MediaProjectionManager

      获取MediaProjection,需要用到MediaProjectionManager,它是一个系统级的服务,类似WindowManager,ActivityManager等,可以通过getSystemService方法来获取它的实例:

MediaProjectionManager mediaProjectionManager =
                    (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
b.请求

      获取到MediaProjectionManager之后,再来进一步获取MediaProjection,获取方式如下:

private void requestPermission() {
    MediaProjectionManager mediaProjectionManager =
                    (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(),
                    REQUEST_MEDIA_PROJECTION);
}

      获取方式是通过startActivityForResult()来获取,createScreenCaptureIntent()是获取请求的Intent,如下:

public Intent createScreenCaptureIntent() {
    Intent i = new Intent();
    i.setClassName("com.android.systemui","com.android.systemui.media.MediaProjectionPermissionActivity");
    return i;
}

      从请求的Intent可以看到,是去启动systemui里面的一个叫MediaProjectionPermissionActivity的Activity。

c.请求处理

      由于屏幕录制会涉及到个人隐私,需要弹窗确认,一起看一下MediaProjectionPermissionActivity的逻辑处理:

public class MediaProjectionPermissionActivity extends Activity
        implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener,
        DialogInterface.OnCancelListener {
    ......
    ......
    @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,
                        false /*permanentGrant*/));
                finish();
                return;
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Error checking projection permissions", e);
            finish();
            return;
        }

        ......
        //弹窗来确认是否赋予权限
        ......
    }
    ......
    ......
}

      通过以上可以看到,在MediaProjectionPermissionActivity创建后,主要做了以下几件事:

  1. 通过ServiceManager获取到MediaProjectionManager引用对象;
  2. 获取调用者的包名、uid等信息,进行检测判断,如果该package已经请求过且同意过,直接调用setResult()返回;否则的话,会弹窗进行确认;

      接下来看一下允许后,执行setResult()的逻辑:

setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName, mPermanentGrant));

private Intent getMediaProjectionIntent(int uid, String packageName, boolean permanentGrant)
            throws RemoteException {
    IMediaProjection projection = mService.createProjection(uid, packageName,
                 MediaProjectionManager.TYPE_SCREEN_CAPTURE, permanentGrant);
    Intent intent = new Intent();
    intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
    return intent;
}

      可以看到,通过getMediaProjectionIntent()来获取Intent,通过前面获取的mService来获取IMediaProjection实例,然后通过asBinder()获取到IMediaProjection实例对应的binder传入Intent,最后返回Intent。

d.请求返回

      处理端MediaProjectionPermissionActivity执行setResult()后,申请端通过onActivityResult来获取结果,data为Intent,通过getMediaProjection来获取MediaProjection。

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode != RESULT_OK) {
        Toast.makeText(this,
                    "User denied screen recorder permission", Toast.LENGTH_SHORT).show();
        return;
    }
    mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
}

      至此MediaProjection已经获取完毕。

Rom开发获取MediaProjection

      如果应用是基于平台开发,不希望录屏时给用户弹窗提示,可以不通过startActivityForResult,从而跳过去MediaProjectionPermissionActivity申请弹窗权限,逻辑如下:

a.应用系统具有系统权限
android:sharedUserId="android.uid.system
b.直接去获取onActivityResult返回的data Intent
public Intent getMediaProjectionIntent(Context context, String packageName,
            boolean permanentGrant) throws RemoteException {
    IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
    sMediaProjectionManager = IMediaProjectionManager.Stub.asInterface(b);
    PackageManager packageManager = context.getPackageManager();
    ApplicationInfo aInfo;
    int uid;
    try {
        aInfo = packageManager.getApplicationInfo(packageName, 0);
        uid = aInfo.uid;
    } catch (PackageManager.NameNotFoundException e) {
        Log.e(TAG, "unable to look up package name", e);
        return null;
    }
    IMediaProjection projection = sMediaProjectionManager.createProjection(uid, packageName,
                MediaProjectionManager.TYPE_SCREEN_CAPTURE, permanentGrant);
    Intent intent = new Intent();
    intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
    return intent;
}
c.获取MediaProjection
Intent data = getMediaProjectionIntent(c, PKG_NAME, true);
mMediaProjection = mProjectionManager.getMediaProjection(Activity.RESULT_OK, data);