一起养成写作习惯!这是我参与「掘金日新计划 · 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创建后,主要做了以下几件事:
- 通过ServiceManager获取到MediaProjectionManager引用对象;
- 获取调用者的包名、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);