アプリでスクリーンショットして画像保存

アプリ開発

アプリでアンドロイド端末のスクリーンショットを撮影して、画像保存するまでを紹介します。

よくあるサンプルだと、アプリを開いてるときのみスクリーンショットしているため、アプリを閉じるとスクリーンショットできないのがほとんどでした。

今回紹介するアプリは、フォアグラウンドサービスでスクリーンショットを撮影するので、アプリを閉じてもスクリーンショットしてくれます。

あと、最新のSDKバージョン (API30 (Android 11 (R))) に対応したソースで書いてあります。

実行するとこのようにアプリを閉じてもスクリーンショットが保存されていきます。

保存されたスクリーンショットの確認
スポンサーリンク

環境

  • Windows 11 Home 21H2
  • Android Studio Bumblebee | 2021.1.1 Patch 1
  • API 30、Android 11 (R)

手順

プロジェクト作成

今回は Empty Activity を使います。

プロジェクトの作成

設定は下記の通りです。

  • Language: Java
  • Minimum SDK: API 30、Android 11 (R)
プロジェクトの設定

ソース

完成品は こちら に置いておきます。

activity_main.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"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/editTextNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="4dp"
        android:ems="10"
        android:inputType="number"
        android:text="10"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="28dp"
        android:text="回"
        app:layout_constraintStart_toEndOf="@+id/editTextNumber"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editTextNumber2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="4dp"
        android:ems="10"
        android:inputType="number"
        android:text="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editTextNumber" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="32dp"
        android:text="秒毎"
        app:layout_constraintStart_toEndOf="@+id/editTextNumber2"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="8dp"
        android:text="実行"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editTextNumber2"
        android:onClick="screenshot"/>
</androidx.constraintlayout.widget.ConstraintLayout>
画面の作成

MainActivity.java

実行ボタンを押したとき、フォアグラウンドで動く CaptureService を作りサービス実行させます。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void screenshot(View view) {
        MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) this.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        activityResultLauncher.launch(mediaProjectionManager.createScreenCaptureIntent());
    }

    ActivityResultLauncher<Intent> activityResultLauncher =
        registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result.getResultCode() == Activity.RESULT_OK) {

                EditText editText1 = findViewById(R.id.editTextNumber);
                int maxCount = Integer.parseInt(editText1.getText().toString());
                EditText editText2 = findViewById(R.id.editTextNumber2);
                int waitSeconds = Integer.parseInt(editText2.getText().toString());

                Intent intent = new Intent(getApplication(), CaptureService.class);
                intent.putExtra("ResultCode", result.getResultCode());
                intent.putExtra("ResultData", result.getData());
                intent.putExtra("MaxCount", maxCount);
                intent.putExtra("WaitSeconds", waitSeconds);
                startForegroundService(intent);
            }
        });
}

CaptureService.java

フォアグラウンドサービスを実行するために、ステータスバーに通知を登録します。これをしないと例外が発生してしまいます。

続いて、スクリーンショットを撮影できるようにするため、メディアの設定をしています。

最後に別スレッドで定期的にスクリーンショット撮影し画像保存するようにしています。

public class CaptureService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

        NotificationChannel channel = new NotificationChannel("channelId", "channelName", NotificationManager.IMPORTANCE_DEFAULT);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        manager.createNotificationChannel(channel);

        Notification notification = new Notification.Builder(this, "channelId")
            .setContentTitle("notification_title")
            .setContentText("notification_message")
            .build();
        startForeground(1, notification);

        DisplayMetrics metrics = getResources().getDisplayMetrics();
        int width = metrics.widthPixels;
        int height = metrics.heightPixels;
        int density = metrics.densityDpi;

        int resultCode = intent.getIntExtra("ResultCode", 0);
        Intent resultData = intent.getParcelableExtra("ResultData");
        int maxCount = intent.getIntExtra("MaxCount", 0);
        int waitSeconds = intent.getIntExtra("WaitSeconds", 0);

        MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) this.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData);
        ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2);
        VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture", width, height, density,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader.getSurface(), null, null);

        Thread thread = new Thread(() -> {
            try {
                for (int i = 0; i < maxCount; i++) {
                    Image image = imageReader.acquireLatestImage();
                    // 画面に変更がない場合、NULL
                    if (image != null) {

                        // Image -> Bitmap
                        Image.Plane[] planes = image.getPlanes();
                        ByteBuffer buffer = planes[0].getBuffer();
                        int pixelStride = planes[0].getPixelStride();
                        int rowStride = planes[0].getRowStride();
                        int rowPadding = rowStride - pixelStride * width;
                        Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
                        bitmap.copyPixelsFromBuffer(buffer);
                        image.close();

                        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath() + "/image_" + System.currentTimeMillis() + ".png");
                        FileOutputStream outStream = new FileOutputStream(file);
                        bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
                        outStream.close();
                    }

                    Log.d("CaptureService", "onStartCommand: " + i);
                    Thread.sleep(waitSeconds * 1000);
                }
            } catch (Exception e) {
                Log.e("CaptureService", e.getMessage());
            }

            stopSelf();
        });
        thread.start();

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
    }
}

AndroidManifest.xml

マニフェストにパーミッションとサービスを宣言します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication">
        <service android:name=".CaptureService"
            android:enabled="true"
            android:exported="false"
            android:foregroundServiceType="mediaProjection" />
        <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>
    </application>

</manifest>

実行

実行するとピクチャフォルダにスクリーンショットが保存されていきます。

ピクチャフォルダにスクリーンショットが保存される

確認

確認のしかたは次の通りです。

Filesアプリを開き、

Filesアプリを開く

左メニューからアンドロイド端末を選択し、

左メニューからアンドロイド端末の選択

Picturesフォルダーを選択し、

Picturesフォルダーの選択

保存されたスクリーンショットが確認できます。

保存されたスクリーンショットの確認

参照

タイトルとURLをコピーしました