Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
В Android 5.0 (API 21) появилась возможность записи видео и звука с экрана мобильного устройства. Пока плохо представляю, кому это нужно. Как вариант, подсунуть телефон коту и записать все его действия. Потом в спокойной обстановке изучить поведение кота и понять смысл...
Для примера достаточно одной кнопки, которая будет запускать процесс записи видео и звука с экрана.
В манифест добавим разрешения на запись звука и запись файла во внешнее хранилище.
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Для удобства создадим отдельную службу.
package ru.alexanderklimov.screencapture;
import android.app.Service;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.os.Binder;
import android.os.Environment;
import android.os.HandlerThread;
import android.os.IBinder;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
public class RecordService extends Service {
private MediaProjection mediaProjection;
private MediaRecorder mediaRecorder;
private VirtualDisplay virtualDisplay;
private boolean running;
private int width = 720;
private int height = 1080;
private int dpi;
@Override
public IBinder onBind(Intent intent) {
return new RecordBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread serviceThread = new HandlerThread("service_thread",
android.os.Process.THREAD_PRIORITY_BACKGROUND);
serviceThread.start();
running = false;
mediaRecorder = new MediaRecorder();
}
@Override
public void onDestroy() {
super.onDestroy();
}
public void setMediaProject(MediaProjection project) {
mediaProjection = project;
}
public boolean isRunning() {
return running;
}
public void setConfig(int width, int height, int dpi) {
this.width = width;
this.height = height;
this.dpi = dpi;
}
public void startRecord() {
if (mediaProjection == null || !running) {
initRecorder();
createVirtualDisplay();
mediaRecorder.start();
running = true;
}
}
public void stopRecord() {
if (running) {
running = false;
mediaRecorder.stop();
mediaRecorder.reset();
virtualDisplay.release();
mediaProjection.stop();
}
}
private void createVirtualDisplay() {
virtualDisplay = mediaProjection.createVirtualDisplay("MainScreen",
width, height, dpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mediaRecorder.getSurface(), null, null);
}
private void initRecorder() {
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setOutputFile(getsaveDirectory() + System.currentTimeMillis() + ".mp4");
mediaRecorder.setVideoSize(width, height);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1024);
mediaRecorder.setVideoFrameRate(30);
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
public String getsaveDirectory() {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String rootDir = Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/" + "ScreenRecord" + "/";
File file = new File(rootDir);
if (!file.exists()) {
if (!file.mkdirs()) {
return null;
}
}
Toast.makeText(getApplicationContext(), rootDir, Toast.LENGTH_SHORT).show();
return rootDir;
} else {
return null;
}
}
class RecordBinder extends Binder {
RecordService getRecordService() {
return RecordService.this;
}
}
}
Не забываем зарегистрировать службу в манифесте.
<service android:name=".RecordService" />
Для записи используется класс MediaRecorder. При записи используется виртуальный экран VirtualDisplay.
Код для активности.
package ru.alexanderklimov.screencapture;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private static final int RECORD_REQUEST = 101;
private static final int STORAGE_REQUEST = 102;
private static final int AUDIO_REQUEST = 103;
private MediaProjectionManager projectionManager;
private RecordService recordService;
private Button recordButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
projectionManager = (MediaProjectionManager)
getSystemService(Context.MEDIA_PROJECTION_SERVICE);
recordButton = findViewById(R.id.button_start);
recordButton.setEnabled(false);
recordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recordService.isRunning()) {
recordService.stopRecord();
recordButton.setText("Start");
} else {
Intent captureIntent = projectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, RECORD_REQUEST);
}
}
});
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_REQUEST);
}
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[] {Manifest.permission.RECORD_AUDIO}, AUDIO_REQUEST);
}
Intent intent = new Intent(this, RecordService.class);
bindService(intent, connection, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RECORD_REQUEST && resultCode == RESULT_OK) {
MediaProjection mediaProjection = projectionManager.getMediaProjection(resultCode, data);
recordService.setMediaProject(mediaProjection);
recordService.startRecord();
recordButton.setText("Stop");
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_REQUEST || requestCode == AUDIO_REQUEST) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
finish();
}
}
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
RecordService.RecordBinder binder = (RecordService.RecordBinder) service;
recordService = binder.getRecordService();
recordService.setConfig(metrics.widthPixels, metrics.heightPixels, metrics.densityDpi);
recordButton.setEnabled(true);
recordButton.setText(recordService.isRunning() ? "Stop" : "Start");
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
}
Запускаем приложение, нажимаем на кнопку и процесс записи начинается. Можно производить различные действия - запускать другие приложения, выдвигать шторку и т.д. Потом повторно нажимаем на кнопку для выключения записи. Файл MP4 сохраняется на внешнем накопителе. Его можно извлечь и посмотреть.